6.8.2 Syntax-rules Macros

syntax-rules macros are simple, pattern-driven syntax transformers, with a beauty worthy of Scheme.

Syntax: syntax-rules literals (pattern template) …

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.

6.8.2.1 Patterns

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:

Scheme Syntax: else
Scheme Syntax: =>
Scheme Syntax: _
Scheme Syntax: ...

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.

6.8.2.2 Hygiene

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.

6.8.2.3 Shorthands

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.

Syntax: define-syntax-rule (keyword . pattern) [docstring] template

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 ...)))

6.8.2.4 Reporting Syntax Errors in Macros

Syntax: syntax-error message [arg ...]

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 ...))))

6.8.2.5 Specifying a Custom Ellipsis Identifier

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)

6.8.2.6 Further Information

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


Footnotes

(14)

Language lawyers probably see the need here for use of literal-identifier=? rather than free-identifier=?, and would probably be correct. Patches accepted.

(15)

Archived from the original on 2013-05-03.