Previous: Syntactic Closures, Up: Macros [Contents][Index]
Explicit renaming is an alternative facility for defining macro
transformers. In the MIT/GNU Scheme implementation, explicit-renaming
transformers are implemented as an abstraction layer on top of syntactic
closures. An explicit-renaming macro transformer is defined by an
instance of the er-macro-transformer
keyword:
The expression is expanded in the syntactic environment of the
er-macro-transformer
expression, and the expanded expression is
evaluated in the transformer environment to yield a macro transformer as
described below. This macro transformer is bound to a macro keyword by
the special form in which the transformer
expression appears (for
example, let-syntax
).
In the explicit-renaming facility, a macro transformer is a procedure that takes three arguments, a form, a renaming procedure, and a comparison predicate, and returns a new form. The first argument, the input form, is the form in which the macro keyword occurred.
The second argument to a transformation procedure is a renaming
procedure that takes the representation of an identifier as its
argument and returns the representation of a fresh identifier that
occurs nowhere else in the program. For example, the transformation
procedure for a simplified version of the let
macro might be
written as
(lambda (exp rename compare) (let ((vars (map car (cadr exp))) (inits (map cadr (cadr exp))) (body (cddr exp))) `((lambda ,vars ,@body) ,@inits)))
This would not be hygienic, however. A hygienic let
macro must
rename the identifier lambda
to protect it from being captured by
a local binding. The renaming effectively creates an fresh alias for
lambda
, one that cannot be captured by any subsequent binding:
(lambda (exp rename compare) (let ((vars (map car (cadr exp))) (inits (map cadr (cadr exp))) (body (cddr exp))) `((,(rename 'lambda) ,vars ,@body) ,@inits)))
The expression returned by the transformation procedure will be expanded in the syntactic environment obtained from the syntactic environment of the macro application by binding any fresh identifiers generated by the renaming procedure to the denotations of the original identifiers in the syntactic environment in which the macro was defined. This means that a renamed identifier will denote the same thing as the original identifier unless the transformation procedure that renamed the identifier placed an occurrence of it in a binding position.
The renaming procedure acts as a mathematical function in the sense that
the identifiers obtained from any two calls with the same argument will
be the same in the sense of eqv?
. It is an error if the renaming
procedure is called after the transformation procedure has returned.
The third argument to a transformation procedure is a comparison
predicate that takes the representations of two identifiers as its
arguments and returns true if and only if they denote the same thing in
the syntactic environment that will be used to expand the transformed
macro application. For example, the transformation procedure for a
simplified version of the cond
macro can be written as
(lambda (exp rename compare) (let ((clauses (cdr exp))) (if (null? clauses) `(,(rename 'quote) unspecified) (let* ((first (car clauses)) (rest (cdr clauses)) (test (car first))) (cond ((and (identifier? test) (compare test (rename 'else))) `(,(rename 'begin) ,@(cdr first))) (else `(,(rename 'if) ,test (,(rename 'begin) ,@(cdr first)) (cond ,@rest))))))))))
In this example the identifier else
is renamed before being passed
to the comparison predicate, so the comparison will be true if and
only if the test expression is an identifier that denotes the same
thing in the syntactic environment of the expression being transformed
as else
denotes in the syntactic environment in which the cond
macro was defined. If else
were not renamed before being passed to
the comparison predicate, then it would match a local variable that
happened to be named else
, and the macro would not be hygienic.
Some macros are non-hygienic by design. For example, the following
defines a loop
macro that implicitly binds exit
to an
escape procedure. The binding of exit
is intended to capture
free references to exit
in the body of the loop, so exit
is not renamed.
(define-syntax loop (er-macro-transformer (lambda (x r c) (let ((body (cdr x))) `(,(r 'call-with-current-continuation) (,(r 'lambda) (exit) (,(r 'let) ,(r 'f) () ,@body (,(r 'f)))))))))
Suppose a while
macro is implemented using loop
, with the
intent that exit
may be used to escape from the while
loop. The while
macro cannot be written as
(define-syntax while (syntax-rules () ((while test body ...) (loop (if (not test) (exit #f)) body ...))))
because the reference to exit
that is inserted by the
while
macro is intended to be captured by the binding of
exit
that will be inserted by the loop
macro. In other
words, this while
macro is not hygienic. Like loop
, it
must be written using the er-macro-transformer
syntax:
(define-syntax while (er-macro-transformer (lambda (x r c) (let ((test (cadr x)) (body (cddr x))) `(,(r 'loop) (,(r 'if) (,(r 'not) ,test) (exit #f)) ,@body)))))
Previous: Syntactic Closures, Up: Macros [Contents][Index]