eval, that spectral hound
Friends, I am not a free man. Eval has been my companion of late, a hellhound on my hack-trail. I give you two instances.
the howl of the-environment, across the ages
(define the-environment (procedure->syntax (lambda (exp env) env)))
Tom Lord inherited this cursed bequest from Jaffer, when he established himself in the nearby earldom of Guile. It so affected him that he added local-eval to Guile, allowing the user to evaluate an expression within a captured local environment:
(define env (let ((x 10)) (the-environment))) (local-eval 'x env) => 10 (local-eval '(set! x 42) env) (local-eval 'x env) => 42
Since then, the tenants of the earldom of Guile have been haunted by this strange leakage of the state of the interpreter into the semantics of Guile.
When the Guile co-maintainer title devolved upon me, I had a plan to vanquish the hound: to compile Guile into fast bytecode. There would be no inefficient association-lists of bindings at run-time. Indeed, there would be no "environment object" to capture. I succeeded, and with Guile 2.0, local-eval, procedure->syntax and the-environment were no more.
But no. As Guile releases started to make it into distributions, and users started to update their code, there arose such a howling on the mailing lists as set my hair on end. The ghost of local-eval was calling: it would not be laid to rest.
I resisted fate, for as long as I could do so in good conscience. In the end, Guile hacker Mark Weaver led an expedition to the mailing list moor, and came back with a plan.
Mark's plan was to have the syntax expander recognize the-environment, and residualize a form that would capture the identities of all lexical bindings. Like this:
(let ((x 10)) (the-environment)) => (let ((x 10)) (make-lexical-environment ;; Procedure to wrap captured environment around ;; an expression wrapper ;; Captured variables: only "x" in this case (list (capture x))))
I'm taking it a little slow because hey, this is some tricky macrology. Let's look at (capture x) first. How do you capture a variable? In Scheme, with a closure. Like this:
;; Capture a variable with a closure. ;; (define-syntax-rule (capture var) (case-lambda ;; When called with no arguments, return the value ;; of VAR. (() var) ;; When called with one argument, set the VAR to the ;; new value. ((new-val) (set! var new-val))))
The trickier part is reinstating the environment, so that x in a local-eval'd expression results in the invocation of a closure. Identifier syntax to the rescue:
;; The wrapper from above: a procedure that wraps ;; an expression in a lexical environment containing x. ;; (lambda (exp) #`(lambda (x*) ; x* is a fresh temporary var (let-syntax ((x (identifier-syntax (_ (x*)) ((set! _ val) (x* val))))) #,exp)))
By now it's clear what local-eval does: it wraps an expression, using the wrapper procedure from the environment object, evaluates that expression, then calls the resulting procedure with the case-lambda closures that captured the lexical variable.
So it's a bit intricate and nasty in some places, but hey, it finally tames the ghostly hound with modern Scheme. We were able to build local-eval on top of Guile's procedural macros, once a couple of accessors were added to our expander to return the set of bound identifiers visible in an expression, and to query whether those bindings were regular lexicals, or macros, or pattern variables, or whatever.
"watson, your service revolver, please."
My initial approach was to produce a correct implementation, and then make it fast. But the JSC maintainers, inspired by the idea that "let is the new var", wanted to ensure that let was fast from the beginning, so that it doesn't get a bad name with developers. OK, fair enough!
Thankfully, there seems to be a developing consensus that eval("let x = 20") will not introduce a new block-scoped lexical. So, down boy. The hound is at bay, for now.
life with dogs