2.2 Lexical Context-Passing Code Generators
Synopsis: As an alternative to syntax templates, code generators can accept a lexical context argument and create syntax by using datum->syntax with the given context.
The basic Code Generators pattern, based on modules and syntax templates, is usually the best way to organize macro auxiliary procedures. Sometimes, however, that pattern does not apply.
One such situation is when there is recursion between the macro and its code generator. Since cycles are disallowed in the module system, it cannot be the case that the macro and its code generators reside in different modules and both refer directly to the other.
The alternative technique for writing code generators is to make the code generators take an extra lexical context parameter. The code generators build their results as S-expression and use datum->syntax to apply the given lexical context. The lexical context argument is supplied by the macro, which has access to the proper bindings (in the correct phase) and can capture them using a syntax template.
This pattern provides code generators with another mechanism for producing syntax with the necessary bindings. On the other hand, the bindings associated with the produced syntax are no longer apparent from the lexical context of some syntax template in the code generator. Reasoning about the bindings involves finding where the context argument originates and reasoning about the bindings it provides.
Historical note: This pattern was much more common before the introduction of for-template imports. Indeed, for a long time the only choice for code generating auxiliaries was between using this pattern and returning an S-expression from the code and letting the calling macro convert it to syntax. The latter option, of course, is equivalent to this pattern; the only difference is who calls datum->syntax.
2.2.1 Example
Let’s revisit the example from the Code Generators section. Here is the code generator module written in the lexical context-passing style:
(module delay-codegen scheme/base |
(provide gen-update-thunk-code) |
; gen-update-thunk-code : stx identifier stx -> stx |
(define (gen-update-thunk-code lctx p-var expr) |
(datum->syntax lctx |
`(lambda () |
(let ([value ,expr]) |
(set-promise-thunk! ,p-var (lambda () value)) |
value)) |
#'here))) |
Compare this code with the code in Code Generators (in the “Separate module” section). Note that the 'delay-codegen module does not require anything for-template. Instead of using a syntax template (via quasisyntax), the code generator constructs an S-expression using quasiquote and then applies the lexical context of lctx using datum->syntax. The S-expression does contain embedded syntax objects for p and expr; the bindings they already carry are unchanged by the call to datum->syntax. The datum->syntax procedure never affects embedded syntax objects.
Note also that we can’t analyze the bindings of the generated code like we could in the Code Generators examples. What is the binding for the occurrence of lambda? Who knows? It depends on the value of lctx. The situation with set-promise-thunk! is especially striking: by itself, this module contains no evidence of a binding of the name set-promise-thunk!. It is part of the implicit contract on lctx that it has bindings for lambda, set-promise-thunk!, etc.
Here is the client module:
(module delay scheme/base |
(require (for-syntax scheme/base |
'delay-codegen)) |
(provide delay) |
(define-struct promise (thunk)) |
(define-syntax (delay stx) |
(syntax-case stx () |
[(delay e) |
#`(letrec ([p (make-promise |
#,(gen-update-thunk-code #'here #'p #'e))]) |
p)]))) |
The macro calls the code generating procedure with the same arguments as before, plus a new argument, #'here, that just captures the lexical context of the module (well, nearly – see below). This supplies the occurrences of lambda and set-promise-thunk! in the gen-update-thunk-code with appropriate bindings.
Context-passing also has the pleasant consequence that once again we can make do with just two modules (the macro module and the auxiliary module) instead of the three required by the standard Code Generators arrangement.
Why is it only nearly the lexical context of the module? Well, in fact, the #'here occurs within the scope of the macro’s binding of the variable stx and the pattern variables delay and e. If gen-update-thunk-code produces references to any of those names, they refer to the local bindings within the macro, not to whatever module-level bindings they might have. It would be prudent to move the occurrence of #'here out of those scopes to lessen the risk of mysterious identifier used out of context errors (see Unresolved pattern tag: "errors").
The use of #'here is conventional for lexical context arguments, but nearly any syntax template would do: #'foo, #'(), #'17; but not #'e – that’s a pattern variable reference, and that refers to a syntax object from a different lexical context entirely. A safer way to write it would be (quote-syntax here); then it would not matter if here were bound as a pattern variable, since quote-syntax does not do pattern variable substitution. But in practice people seem to carefully take their chances with #'here.
2.2.2 Recursive evaluator example
???