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

Reflective procedures

a reflective procedure is written (lambda reflect (m l e r k) body). it is applied to its own call site rather than to values: the five things a handler gets, except that e is the list of operand forms, unevaluated.

(define twice
  (lambda reflect (m l e r k)
    (meta m l 'base-eval (car e) r
          (cont-in k (lambda (v) (apply-cont k (+ v v)))))))

(say (twice (+ 1 2)))
6

meta evaluates something: interpreter, stage, handler name, form, environment, continuation. cont-in k f builds a continuation whose normal arm is f and whose raise arm is k’s. apply-cont k v hands v to the call site.

never evaluating the operand gives a macro, near enough:

(define quoted (lambda reflect (m l e r k) (apply-cont k (car e))))

(say (quoted (+ 1 2)))
('+ 1 2)

except that this is not a rewriting pass. it runs at the meta level when the call is reached, with the environment and continuation in hand, neither of which a macro can see.

Not calling k

the normal arm of a reflective body’s continuation is the identity, not k. a body that returns without going through apply-cont returns to the nearest prompt instead of to the call site:

(define bail (lambda reflect (m l e r k) (car e)))

(say (prompt (lambda () (+ 1 (bail this-is-syntax)))))
'this-is-syntax

the (+ 1 _) is gone. that is abort, which in the prelude is exactly this:

(define abort
  (lambda reflect (m l e r k)
    (meta m l 'base-eval (car e) r id-cont)))

What is built this way

none of the following is a special form. all are naj/prelude.naj.

(define call/cc
  (lambda reflect (m l e r k)
    (meta m l 'base-eval (car e) r
          (cont-in k (lambda (f) (apply l f (list k) k))))))
narju> (call/cc (lambda (k) (+ 1 (k 41))))
41

narju> (+ 1 (call/cc (lambda (k) 41)))
42

prompt delimits by applying its thunk under id-cont rather than the caller’s k. it must be reflective for that: an ordinary function would already be running under the continuation it means to delimit.

attempt runs a thunk under a continuation whose two arms tag their answers:

narju> (attempt (lambda () (+ 1 (throw 'boom))))
('throw . 'boom)

narju> (attempt (lambda () (+ 1 2)))
('ok . 3)

await reifies the call site and hands it to whoever runs the prompt, which is how an RPC suspends a task without blocking a thread. see Calls and replies.

(define interpreter (lambda reflect (m l e r k) (apply-cont k m)))
(define environment (lambda reflect (m l e r k) (apply-cont k r)))

Second class

a reflective procedure handles a call site, so it needs one. in operator position eval-app sees the tag and routes to apply-reflective before evaluating operands. reached any other way, by a floor application or from compiled code, there is no syntax to hand it and static-apply throws ('no-call-site . f).

this is the one thing compiling does not preserve. by the time compiled code applies an operator the operands are values and the tag is gone, so a reflective call site inside a clambda resolves while staging or not at all.