The Revised5 report on the algorithmic language Scheme includes a mechanism by which programmers can introduce (and immediately begin to use) new types of expressions, modifying the very syntax of the language to simplify or regularize the subsequent code. These new types of derived expressions are called macros.
To define a macro, the programmer specifies its syntax rules. Each such rule consists of a pattern that exhibits one possible structure for the expression and a template that shows how the components of that structure can be rearranged to form an expression of some type that Scheme already understands. When it sees the macro definition, the Scheme processor memorizes these syntax rules. Subsequently, whenever it sees an expression that matches one of the patterns, it automatically rearranges the components as prescribed by the corresponding template, then evaluates the result.
For instance, suppose that we wanted to introduce the
until-expression into Scheme -- an expression comprising
the new keyword until, a test expression, and a body of zero
or more expressions:
> (define counter 10)
> (until (zero? counter)
(display counter)
(newline)
(set! counter (- counter 1)))
10
9
8
7
6
5
4
3
2
1
#t
When evaluated, an until-expression begins by evaluating the
test. If the value of the test is truish (that is, anything other than
#f, its value becomes the value of the entire
until-expression; otherwise, the expressions in the body are
evaluated, in order, for side effects only (their values being discarded),
and then the test is evaluated again, with the same alternative
consequences as before. So the until-expression shown above
is equivalent to
(letrec ((loop (lambda ()
(let ((test-result (zero? counter)))
(if test-result
test-result
(begin
(display counter)
(newline)
(set! counter (- counter 1))
(loop)))))))
(loop))
The pattern of an until-expression is
(until test body ...)
The identifier ... has a special interpretation in a pattern:
It indicates that the pattern will match zero or more occurrences of the
subpattern that immediately precedes it. (So body can match
any number of subexpressions of an until-expression.)
In the example above, the identifier test in the pattern
matches the subexpression (zero? counter), and
body ... matches the expression sequence (display
counter) (newline) (set! counter (- counter 1)).
The template that describes how to rearrange this pattern is
(letrec ((loop (lambda ()
(let ((test-result test))
(if test-result
test-result
(begin
body ...
(loop)))))))
(loop))
When we replace test in this template by (zero?
counter), and body ... by (display
counter) (newline) (set! counter (- counter 1)), we get exactly the
letrec-expression presented earlier as a semantic equivalent
for the sample until-expression.
Since an until-expression has only one form, the macro
definition has only one syntax rule. The complete macro definition looks
like this:
(define-syntax until
(syntax-rules ()
((until test body ...)
(letrec ((loop (lambda ()
(let ((test-result test))
(if test-result
test-result
(begin
body ...
(loop)))))))
(loop)))))
The keyword define-syntax introduces a macro definition, and
the identifier that follows it becomes the keyword in the newly defined
expression type. The construction after that is a transformer
specification -- a list beginning with the keyword
syntax-rules, followed by a list containing any identifiers
that are to be treated as ``internal keywords'' (like else in
the syntax of cond-expressions), and then zero or more syntax
rules, each syntax rule being a two-element list consisting of a pattern
and the corresponding template.
The macro definition that introduces receive-expressions, as
used in Algorithms for functional programming, is even easier. The
basic idea is to simplify the cumbersome syntax of calls to the
procedure for transmitting multiple values, call-with-values.
Instead of writing, say,
(call-with-values (lambda () (values (sin x) (cos x))) (lambda (sine cosine) (* 2 (square sine) (square cosine))))
we'd prefer to think of sine and cosine as
variables bound locally to the multiple values of the expression
(values (sin x) (cos x), in a construction that looks a little
like a let-expression:
(receive (sine cosine) (values (sin x) (cos x)) (* 2 (square sine) (square cosine)))
For greatest generality, we should allow the keyword receive
to be followed by a list of identifiers (one for each value of the
the multiple-valued expression), by a single identifier that is bound to a
list of all the values of the multiple-valued expression, or by a dotted
list in which the identifiers preceding the dot are bound to individual
values of the multiple-valued expression and the identifier after the dot
to a list of all the rest.
Here is the macro definition that adds receive-expressions to
the language:
(define-syntax receive
(syntax-rules ()
((receive formals expression body ...)
(call-with-values (lambda () expression)
(lambda formals body ...)))))
The identifier formals in the pattern matches whatever comes
after the keyword receive, whether it is a list, a single
identifier, or a dotted list. When the template is filled in, the
component that matches formals is dropped in behind
the keyword lambda in the second argument (the ``consumer'')
in the call to call-with-values. Similarly,
expression matches the multiple-valued source expression,
which is dropped into the body of the ``producer'' argument to
call-with-values; and body ... matches the
sequence of expressions in the body of the receive-expression,
which are dropped into the body of the ``consumer''.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/algorithms/macros-in-Scheme.xhtml
created September 5, 2000
last revised September 6, 2000