Macros in Scheme

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.

Example: until-expressions

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.

Example: receive-expressions

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

John David Stone (stone@cs.grinnell.edu)