[racket] metacircular interpreter: hygiene, lexical variable, and substitution ==> Re: recursive macro expanding order

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Mon Dec 13 03:13:09 EST 2010

YC wrote:
> On Sat, Dec 11, 2010 at 2:49 PM, YC <yinso.chen at gmail.com> wrote:
>> On Sat, Dec 11, 2010 at 8:56 AM, Ryan Culpepper <ryanc at ccs.neu.edu> wrote:
>>> If you are only supporting syntax-rules, then I recommend implementing the
>>> algorithm from "Macros that Work" by Clinger and Rees. Hygienic macro
>>> expansion does typically involve alpha-conversion---renaming lexical
>>> variables to fresh names. The challenge, as you mention above, is in not
>>> renaming too eagerly.
> Looking at the paper, it occurred to me that one way to ensure that macro
> expands to the correct binding is for the macro to carry the references to
> the environment itself.
> So for the example in the paper:
> (let-syntax ((push (syntax-rules
>                        ((push ?v ?x)
>                         =>
>                         (set! ?x (cons ?v ?x))))))
>   (let ((pros (list "cheap" "fast"))
>         (cons (list)))
>     (push "unreliable" cons)))
> The push macro already contains the bindings to set! and cons, so the later
> shadowing of cons does not overwrite push's own copy of cons.  In that sense
> a macro transformer's job is to merge its own environment along with the
> call-site's environment.
> The key to correctly implement macros is then to ensure that every macro
> holds references to a environment at the point of its definition, so it can
> then be used for merging during the expansion.
> Is this a fair way of thinking about macro expansions?

Not quite. There might not be a single environment for the macro. This 
can happen if the macro definition itself (in particular, the macro's 
template) is introduced by some other macro. It is possible to have a 
macro definition contain two occurrences of the same symbol that refer 
to different bindings.

Beyond that, they should also be treated differently if used as binding 
occurrences. It is a common trick when limited to syntax-rules to 
construct a list of fresh names by recursively adding the same symbol to 
an accumulator list. Since each occurrence of the symbol is introduced 
by a different macro step, they're all different for the purpose of binding.

(define-syntax with-fresh-names
   (syntax-rules ()
     [(with-fresh-names () names k k-arg)
      (k names k-arg)]
     [(with-fresh-names (thing . rest) names k k-arg)
      (with-fresh-names rest (tmp . names) k k-arg)]))

You might decide that instead of each macro closing over its 
environment, each subterm of the template must close over its 
environment. That gets you to the *syntactic closures* macro system 
(also in SLIB). Long story short, it's effectively impossible to use. 
IIRC, the critical mistake of syntactic closures is assuming that the 
common case is that a macro knows the complete environment of its input 
subexpressions. That's true of the 'or' macro (hygiene test case #1), 
but it makes writing new binding forms difficult.

If you take syntactic closures and relax the notion of "environment" to 
instead be "what seems to be the environment, given the expander's 
current state of knowledge" and adjusting the syntax representation and 
expander accordingly, you get the Dybvig-Hieb-Bruggeman algorithm ("the 
syntax-case algorithm").


Posted on the users mailing list.