Stock handlers
the prelude ships four handlers to hand a task. only two of them answer
('stop): a table and a greeter do, a ref and a future do not, and a
task running one never ends on its own. that is why the examples below are at
the prompt. a script that spawns a ref and finishes parks rather than exits.
ref
the object language has no mutable cell. a ref stands in its place: the state
is a task’s and the name for it is an address.
narju> (define r (spawn (lambda (me) (ref me 0))))
'r
narju> (call r '(read))
0
narju> (call r '(write 7))
'ok
narju> (call r '(read))
7
not a cell with extra steps. it crosses a network link unchanged, it can be monitored, and a write to one that has died raises rather than succeeding quietly against nothing.
there is deliberately no update-by-function. a closure cannot be sent, which puts read-modify-write out of reach of one message, and two messages are not atomic against another caller. an operation that must be atomic belongs in the task’s own handler.
table
(define t (spawn (lambda (me) (table-at me '()))))
(say (call t '(put a 1)))
(say (call t '(get a)))
(say (call t '(keys)))
(say (attempt (lambda () (call t '(get b)))))
(say (call t '(drop a)))
(say (call t '(keys)))
(say (call t '(stop)))
'ok
1
('a)
('throw 'no-such-key . 'b)
'ok
nil
'ok
put also works as a cast, so a task can add itself to a table without making
a call.
greeter
a table with a rendezvous protocol on top, meant for task 0 of a node. it is the one thing a peer arriving over a link cannot do for itself: turn a name into an address.
narju> (define hall (spawn (lambda (me) (greet me '()))))
'hall
narju> (define r (spawn (lambda (me) (ref me 'hello))))
'r
narju> (call hall (list 'register 'store r))
'ok
narju> (call hall '(names))
('store)
narju> (call (call hall '(lookup store)) '(read))
'hello
narju> (attempt (lambda () (call hall '(lookup nope))))
('throw 'no-such-name . 'nope)
register is also a cast, because the task most likely to want it cannot make
a call yet: a call suspends into a loop, and a child naming itself on its way
into its own loop has nothing to suspend into.
it is a rendezvous and not a guard. a link posts to a task by number, so a peer can already reach anything in the heap. trust is per link, not per task.
future
covered in Calls and replies. a task that holds one outstanding call
and then becomes a reference on the answer.
Changing behaviour
hand replaces the handler. what waited in the backlog goes with it, which is
what makes it more than a convenience: a task is addressable from the instant
it is spawned, so requests that arrived before it was ready are answered by
what it became.
narju> (define t
(spawn (lambda (me)
(begin (send me 'boot)
(task-loop me
(lambda h (st msg)
(begin (if (eq? msg 'boot) 'ok (send me msg))
(hand reference 'ready)))
0)))))
't
narju> (call t '(read))
'ready
narju> (call t '(write 9))
'ok
narju> (call t '(read))
9
the kick may lose the race, since the task is addressable the instant it is
spawned, so the first turn hands back whatever woke it rather than spending
that message on booting. future does the same dance for the same reason.
become replaces the semantics:
(define (loud i)
(with-handler i 'eval-var
(lambda h (m l e r k)
(begin (say (list 'read e)) ((handler-of i 'eval-var) m l e r k)))))
(define t
(spawn (lambda (me)
(task-loop me
(lambda h (n msg)
(if (eq? (verb (req-body msg)) 'loud)
(begin (reply msg 'ok) (become loud n))
(begin (reply msg n) (cons 'stop n))))
7))))
(say (call t '(loud)))
(say (call t '(read)))
'ok
('read 'eq?)
('read 'verb)
('read 'req-body)
('read 'msg)
('read 'reply)
('read 'msg)
('read 'n)
('read 'cons)
7
('read 'n)
from the next turn on, that task’s handler runs under (loud I) and announces
every variable it reads. the turn that asked for it is unaffected, having
already been given its semantics. this is the answer to a turn rather than
something inside one, on purpose: a change of meaning mid-computation is the
thing most hostile to compilation. only the handler changes, so the loop, the
prompt and any suspended continuation keep the semantics they were made under,
which is why the trace stops at the handler’s own body.