Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.