EM
EM — execute 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.