YC wrote:
On Sat, Dec 11, 2010 at 2:49 PM, YC <yinso.c...@gmail.com> wrote:

On Sat, Dec 11, 2010 at 8:56 AM, Ryan Culpepper <ry...@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").

Ryan

_________________________________________________
 For list-related administrative tasks:
 http://lists.racket-lang.org/listinfo/users

Reply via email to