syntax-rules
macros are simple, pattern-driven syntax transformers, with
a beauty worthy of Scheme.
Create a syntax transformer that will rewrite an expression using the rules embodied in the pattern and template clauses.
A syntax-rules
macro consists of three parts: the literals (if any), the
patterns, and as many templates as there are patterns.
When the syntax expander sees the invocation of a syntax-rules
macro, it
matches the expression against the patterns, in order, and rewrites the
expression using the template from the first matching pattern. If no pattern
matches, a syntax error is signaled.
We have already seen some examples of patterns in the previous section:
(unless condition exp ...)
, (my-or exp)
, and so on. A pattern is
structured like the expression that it is to match. It can have nested structure
as well, like (let ((var val) ...) exp exp* ...)
. Broadly speaking,
patterns are made of lists, improper lists, vectors, identifiers, and datums.
Users can match a sequence of patterns using the ellipsis (...
).
Identifiers in a pattern are called literals if they are present in the
syntax-rules
literals list, and pattern variables
otherwise. When
building up the macro output, the expander replaces instances of a pattern
variable in the template with the matched subexpression.
(define-syntax kwote (syntax-rules () ((kwote exp) (quote exp)))) (kwote (foo . bar)) ⇒ (foo . bar)
An improper list of patterns matches as rest arguments do:
(define-syntax let1 (syntax-rules () ((_ (var val) . exps) (let ((var val)) . exps))))
However this definition of let1
probably isn’t what you want, as the tail
pattern exps will match non-lists, like (let1 (foo 'bar) . baz)
. So
often instead of using improper lists as patterns, ellipsized patterns are
better. Instances of a pattern variable in the template must be followed by an
ellipsis.
(define-syntax let1 (syntax-rules () ((_ (var val) exp ...) (let ((var val)) exp ...))))
This let1
probably still doesn’t do what we want, because the body
matches sequences of zero expressions, like (let1 (foo 'bar))
. In this
case we need to assert we have at least one body expression. A common idiom for
this is to name the ellipsized pattern variable with an asterisk:
(define-syntax let1 (syntax-rules () ((_ (var val) exp exp* ...) (let ((var val)) exp exp* ...))))
A vector of patterns matches a vector whose contents match the patterns, including ellipsizing and tail patterns.
(define-syntax letv (syntax-rules () ((_ #((var val) ...) exp exp* ...) (let ((var val) ...) exp exp* ...)))) (letv #((foo 'bar)) foo) ⇒ bar
Literals are used to match specific datums in an expression, like the use of
=>
and else
in cond
expressions.
(define-syntax cond1 (syntax-rules (=> else) ((cond1 test => fun) (let ((exp test)) (if exp (fun exp) #f))) ((cond1 else exp exp* ...) (begin exp exp* ...)) ((cond1 test exp exp* ...) (if test (begin exp exp* ...))))) (define (square x) (* x x)) (cond1 10 => square) ⇒ 100 (let ((=> #t)) (cond1 10 => square)) ⇒ #<procedure square (x)>
A literal matches an input expression if the input expression is an identifier with the same name as the literal, and both are unbound14.
Although literals can be unbound, usually they are bound to allow them to be imported, exported, and renamed. See Modules, for more information on imports and exports. In Guile there are a few standard auxiliary syntax definitions, as specified by R6RS and R7RS:
Auxiliary syntax definitions.
These are defined with a macro that never matches, e.g.:
(define-syntax else (syntax-rules ()))
If a pattern is not a list, vector, or an identifier, it matches as a literal,
with equal?
.
(define-syntax define-matcher-macro (syntax-rules () ((_ name lit) (define-syntax name (syntax-rules () ((_ lit) #t) ((_ else) #f)))))) (define-matcher-macro is-literal-foo? "foo") (is-literal-foo? "foo") ⇒ #t (is-literal-foo? "bar") ⇒ #f (let ((foo "foo")) (is-literal-foo? foo)) ⇒ #f
The last example indicates that matching happens at expansion-time, not at run-time.
Syntax-rules macros are always used as (macro . args)
, and
the macro will always be a symbol. Correspondingly, a syntax-rules
pattern must be a list (proper or improper), and the first pattern in that list
must be an identifier. Incidentally it can be any identifier – it doesn’t have
to actually be the name of the macro. Thus the following three are equivalent:
(define-syntax when (syntax-rules () ((when c e ...) (if c (begin e ...))))) (define-syntax when (syntax-rules () ((_ c e ...) (if c (begin e ...))))) (define-syntax when (syntax-rules () ((something-else-entirely c e ...) (if c (begin e ...)))))
For clarity, use one of the first two variants. Also note that since the pattern
variable will always match the macro itself (e.g., cond1
), it is actually
left unbound in the template.
syntax-rules
macros have a magical property: they preserve referential
transparency. When you read a macro definition, any free bindings in that macro
are resolved relative to the macro definition; and when you read a macro
instantiation, all free bindings in that expression are resolved relative to the
expression.
This property is sometimes known as hygiene, and it does aid in code cleanliness. In your macro definitions, you can feel free to introduce temporary variables, without worrying about inadvertently introducing bindings into the macro expansion.
Consider the definition of my-or
from the previous section:
(define-syntax my-or (syntax-rules () ((my-or) #t) ((my-or exp) exp) ((my-or exp rest ...) (let ((t exp)) (if t t (my-or rest ...))))))
A naive expansion of (let ((t #t)) (my-or #f t))
would yield:
(let ((t #t)) (let ((t #f)) (if t t t))) ⇒ #f
Which clearly is not what we want. Somehow the t
in the definition is
distinct from the t
at the site of use; and it is indeed this distinction
that is maintained by the syntax expander, when expanding hygienic macros.
This discussion is mostly relevant in the context of traditional Lisp macros (see Lisp-style Macro Definitions), which do not preserve referential transparency. Hygiene adds to the expressive power of Scheme.
One often ends up writing simple one-clause syntax-rules
macros.
There is a convenient shorthand for this idiom, in the form of
define-syntax-rule
.
Define keyword as a new syntax-rules
macro with one clause.
Cast into this form, our when
example is significantly shorter:
(define-syntax-rule (when c e ...) (if c (begin e ...)))
Report an error at macro-expansion time. message must be a string literal, and the optional arg operands can be arbitrary expressions providing additional information.
syntax-error
is intended to be used within syntax-rules
templates. For example:
(define-syntax simple-let (syntax-rules () ((_ (head ... ((x . y) val) . tail) body1 body2 ...) (syntax-error "expected an identifier but got" (x . y))) ((_ ((name val) ...) body1 body2 ...) ((lambda (name ...) body1 body2 ...) val ...))))
When writing macros that generate macro definitions, it is convenient to
use a different ellipsis identifier at each level. Guile allows the
desired ellipsis identifier to be specified as the first operand to
syntax-rules
, as specified by SRFI-46 and R7RS. For example:
(define-syntax define-quotation-macros (syntax-rules () ((_ (macro-name head-symbol) ...) (begin (define-syntax macro-name (syntax-rules ::: () ((_ x :::) (quote (head-symbol x :::))))) ...)))) (define-quotation-macros (quote-a a) (quote-b b) (quote-c c)) (quote-a 1 2 3) ⇒ (a 1 2 3)
For a formal definition of syntax-rules
and its pattern language, see
See Macros in Revised(5) Report on the Algorithmic Language
Scheme.
syntax-rules
macros are simple and clean, but do they have limitations.
They do not lend themselves to expressive error messages: patterns either match
or they don’t. Their ability to generate code is limited to template-driven
expansion; often one needs to define a number of helper macros to get real work
done. Sometimes one wants to introduce a binding into the lexical context of the
generated code; this is impossible with syntax-rules
. Relatedly, they
cannot programmatically generate identifiers.
The solution to all of these problems is to use syntax-case
if you need
its features. But if for some reason you’re stuck with syntax-rules
, you
might enjoy Joe Marshall’s
syntax-rules
Primer for the Merely Eccentric.15
Language
lawyers probably see the need here for use of literal-identifier=?
rather
than free-identifier=?
, and would probably be correct. Patches
accepted.
Archived from the original on 2013-05-03.