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

EM

EMexecute at the meta level, the name inherited from Black by way of lms-black — evaluates its argument one level up: in the environment of the interpreter that is running the current level.

(EM 42)
;=> 42
(EM (+ 1 1))
;=> 2

Nothing interesting so far — level 1 has the same primitives. The difference is where names live:

(EM (define counter 0))
;=> 'counter
counter
;=> ('unbound . 'counter)
(EM counter)
;=> 0
(EM (EM counter))
;=> ('unbound . 'counter)

counter exists at level 1 only. Level 0 does not see it, and neither does level 2 — each level’s environment is its own. (EM at the prompt is an ordinary form, so its result prints; define returns the defined name.) The levels continue upward on demand — the first EM is what materializes level 2, per the last chapter’s lazy slot, and a nested EM reaches it:

(EM (EM (define deep 7)))
;=> 'deep
(EM (EM deep))
;=> 7
(EM deep)
;=> ('unbound . 'deep)

The interpreter is in scope

Level 1’s environment is not empty when the session starts: it is the frame the handlers live in. (EM (define counter 0)) put counter in the same association list as eval-var — so at level 1, the running interpreter’s parts are ordinary variables, and set! on them is interpreter surgery. This is the tower’s payoff, and the rest of the chapter is one worked example: counting variable lookups, following the instrumentation examples of the lms-black test suite.

An interpreted handler receives the three arguments the dispatch passes: the expression, the environment, and the continuation. The replacement below counts lookups of the name n and defers everything — including the lookup itself — to the saved original:

(define fib (lambda (n) (if (eq? n 0) 1 (if (eq? n 1) 1 (+ (fib (- n 1)) (fib (- n 2)))))))
(fib 5)
;=> 8
(EM (define old-eval-var eval-var))
;=> 'old-eval-var
(EM (set! eval-var (lambda (e r k) (begin (if (eq? e 'n) (set! counter (+ counter 1)) 0) (old-eval-var e r k)))))
;=> 'eval-var
(fib 3)
;=> 3
(EM counter)
;=> 13

Thirteen: (fib 3) calls fib at n = 3, 2, 1, 1, 0, and the body reads n four times per recursive call ((eq? n 0), (eq? n 1), (- n 1), (- n 2)), twice when the second test succeeds, once when the first does — 4+4+2+2+1. fib itself was not touched, or even re-evaluated; the meaning of variable lookup changed under it.

There is a cost, and it is the tower made visible: the replacement handler is an interpreted closure at level 1, so while it is installed, every level-0 variable lookup is interpreted by level 2’s handlers rather than dispatched natively. Restoring the original restores native dispatch:

(EM (set! eval-var old-eval-var))
;=> 'eval-var
(fib 3)
;=> 3
(EM counter)
;=> 13

The counter stands still — fib runs under the stock interpreter again. Rebinding a handler affects evaluation while it is bound, and interpreted code always reads the current handler cell. What happens to code that was compiled while the counting handler was installed is a different question, and the next chapter’s central one.