The following forms establish bindings of name and a macro transformer created by transformer-spec. The binding introduced by these forms shadows a binding of name established in outer scope, if there’s any.
For toplevel bindings, it will shadow bindings of name imported or inherited from other modules (see Modules). (Note: This toplevel shadowing behavior is Gauche’s extension; in R7RS, you shouldn’t redefine imported bindings, so the portable code should avoid it.)
The effect is undefined if you bind the same name more than once in the same scope.
The transformer-spec can be either one of
er-macro-transformer form, or another macro keyword
or syntactic keyword. We’ll explain them later.
[R7RS] If this form appears in toplevel, it binds toplevel name to a macro transformer defined by transformer-spec.
If this form appears in the declaration part of
lambda (internal define-syntax),
other similar forms, it binds name locally within that body.
define-syntaxes are converted to
just like internal
defines are converted to
Defines local macros. Each name is bound to a macro
transformer as specified by the corresponding transformer-spec,
body is expanded. With
transformer-spec is evaluated with the scope
let-syntax, while with
the bindings of names are included in the scope where
transformer-spec is evaluated. Thus
allows mutually recursive macros.
The trasformer-spec is a special expression that evaluates to a macro transformer. It is evaluated in a different phase than the other expressions, since macro transformers must be executed during compiling. So there are some restrictions.
At this moment, only one of the following expressions are allowed:
syntax-rulesform. This is called “high-level” macro, for it uses pattern matching entirely, which is basically a different declarative language from Scheme, thus putting the complication of the phasing and hygiene issues completely under the hood. Some kind of macros are easier to write in
syntax-rules. See Syntax-rules pattern langauge, for further description.
er-macro-transformerform. This employs explicit-renaming (ER) macro, where you can use arbitrary Scheme code to transform the program, with required renaming to keep hygienity. The legacy Lisp macro can also be written with ER macro if you don’t use renaming. See Explicit-renaming macro transformer, for the details.
(define-syntax si if) (define écrivez write) (si (< 2 3) (écrivez "oui"))
|• Syntax-rules pattern langauge:|
|• Explicit-renaming macro transformer:|
[R7RS] This specifies a macro transformer by pattern matching.
Creates a macro transformer from the given procedure-expr.
The created macro transformer has to be bound to the syntactic keyword
Other use of macro transformers is undefined.
The procedure-expr must evaluate to a procedure that takes three arguments; form, rename and id=?.
The form argument receives the S-expression of
the macro call. The procedure-expr must return an
S-expression as the result of macro expansion. This part is pretty much
like the traditional lisp macro. In fact, if you ignore rename
and id=?, the semantics is the same as the traditional
(unhygienic) macro. See the following example
(Note the use of
match; it is a good
tool to decompose macro input):
(use util.match) ;; Unhygienic 'when-not' macro (define-syntax when-not (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) `(if (not ,test) (begin ,expr1 ,@expr))] [_ (error "malformed when-not:" form)])))) (macroexpand '(when-not (foo) (print "a") 'boo)) ⇒ (if (not (foo)) (begin (print "a") 'boo))
This is ok as long as you know you don’t need hygiene—e.g. when
you only use this macro locally in your code, knowning all the
macro call site won’t contain name conflicts. However, if you
when-not macro for general use,
you have to protect namespace pollution around the macro use.
For example, you want to make sure your macro work even if it is
used as follows:
(let ((not values)) (when-not #t (print "This shouldn't be printed")))
The rename argument passed to procedure-expr is a procedure that takes a symbol (or, to be precise, a symbol or an identifier) and effectively renames it to a unique identifier that keeps identity within the macro definition environment and won’t be affected in the macro use environment.
As a rule of thumb, you have to pass
all new identifiers you insert into macro output to the
rename procedure to keep hygiene. In our
begin into the macro output,
so our hygienic macro would look like this:
(define-syntax when-not (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) `(,(rename 'if) (,(rename 'not) ,test) (,(rename 'begin) ,expr1 ,@expr))] [_ (error "malformed when-not:" form)]))))
This is cumbersome and makes it hard to read the macro, so Gauche
provides an auxiliary macro
quasirename, which works like
quasiquote but renaming identifiers in the form. See the
quasirename below for the details. You can write
when-not as follows:
(define-syntax when-not (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) (quasirename rename (if (not ,test) (begin ,expr1 ,@expr)))] [_ (error "malformed when-not:" form)]))))
You can intentionally break hyginene by inserting a symbol
without renaming. The following code implements
when, meaning the result of the
test expression is available in the expr1 exprs …
with the name
it. Since the binding of the identifier
does not exist in the macro use site, but rather injected into
the macro use site by the macro expander, it is unhygienic.
(define-syntax awhen (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) `(,(rename 'let1) it ,test ; 'it' is not renamed (,(rename 'begin) ,expr1 ,@expr))]))))
If you use
quasirename, you can write
,'it to prevent
it from being renamed:
(define-syntax awhen (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) (quasirename rename (let1 ,'it ,test (begin ,expr1 ,@expr)))]))))
Here’s an example:
(awhen (find odd? '(0 2 8 7 4)) (print "Found odd number:" it)) ⇒ prints Found odd number:7
Finally, the id=? argument to the procedure-expr is
a procedure that takes two arguments, and returns
both are identifiers and either both are referring to the same binding
or both are free. It can be used to compare literal syntactic keyword
case forms) hygienically.
if=> macro behaves like
if, except that
(if=> test => procedure) syntax,
procedure is called with the value of
if it is not false. The symbol
=> must match hygienically,
that is, it must refer to the same binding as in the macro definition.
(define-syntax if=> (er-macro-transformer (^[form rename id=?] (match form [(_ test a b) (if (id=? (rename '=>) a) (quasirename rename (let ((t ,test)) (if t (,b t)))) (quasirename rename (if ,test ,a ,b)))]))))
(rename '=>) returns an identifier that captures
the binding of
=> in the macro definition, and using
id=? with the thing passed to the macro argument
checks if both refer to the same binding.
(if=> 3 => list) ⇒ (3) (if=> #f => list) ⇒ #<undef> ;; If the second argument isn't =>, if=> behaves like ordinary if: (if=> #t 1 2) ⇒ 1 ;; The binding of => in macro use environment differs from ;; the macro definition environment, so this if=> behaves like ;; ordinary if, instead of recognizing literal =>. (let ((=> 'oof)) (if=> 3 => list)) ⇒ oof
It works like quasiquote, except that the symbols and identifiers
that appear in the “literal” portion of form (i.e. outside
unquote-splicing) are replaced
by the result of applying rename on themselves.
For example, a form:
(quasirename r (a ,b c "d"))
would be equivalent to write:
(list (r 'a) b (r 'c) "d")
This is not specifically tied to macros; the renamer can be any procedure that takes one symbol or identifier argument:
(quasirename (^[x] (symbol-append 'x: x)) (+ a ,(+ 1 2) 5)) ⇒ (x:+ x:a 3 5)
However, it comes pretty handy to construct the result form in ER macros. Compare the following two:
(use util.match) ;; using quasirename (define-syntax swap (er-macro-transformer (^[f r c] (match f [(_ a b) (quasirename r (let ((tmp ,a)) (set! ,a ,b) (set! ,b tmp)))])))) ;; not using quasirename (define-syntax swap (er-macro-transformer (^[f r c] (match f [(_ a b) `((r'let) (((r'tmp) ,a)) ((r'set!) ,a ,b) ((r'set!) ,b (r'tmp)))]))))