16 Macros
On this page:
top
up

16.1Pattern-Based MacrosπŸ”— i

A pattern-based macro replaces any code that matches a pattern to an expansion that uses parts of the original syntax that match parts of the pattern.

16.1.1define-syntax-rule πŸ”— i

The simplest way to create a macro is to use define-syntax-rule :

(define-syntax-rule patterntemplate)

As a running example, consider the swap macro, which swaps the values stored in two variables. It can be implemented using define-syntax-rule as follows:

The macro is “un-Rackety” in the sense that it involves side effects on variables—but the point of macros is to let you add syntactic forms that some other language designer might not approve.

(let ([tmpx])
(set! xy)
(set! ytmp)))

The define-syntax-rule form binds a macro that matches a single pattern. The pattern must always start with an open parenthesis followed by an identifier, which is swap in this case. After the initial identifier, other identifiers are macro pattern variables that can match anything in a use of the macro. Thus, this macro matches the form (swapform1form2) for any form1 and form2.

Macro pattern variables are similar to pattern variables for match. See Pattern Matching.

After the pattern in define-syntax-rule is the template. The template is used in place of a form that matches the pattern, except that each instance of a pattern variable in the template is replaced with the part of the macro use the pattern variable matched. For example, in

(swapfirstlast)

the pattern variable x matches first and y matches last, so that the expansion is

(let ([tmpfirst])
(set! firstlast)
(set! lasttmp))

16.1.2Lexical ScopeπŸ”— i

Suppose that we use the swap macro to swap variables named tmp and other:

(let ([tmp5]
[other6])
(swaptmpother)
(list tmpother))

The result of the above expression should be (65). The naive expansion of this use of swap, however, is

(let ([tmp5]
[other6])
(let ([tmptmp])
(set! tmpother)
(set! othertmp))
(list tmpother))

whose result is (56). The problem is that the naive expansion confuses the tmp in the context where swap is used with the tmp that is in the macro template.

Racket doesn’t produce the naive expansion for the above use of swap. Instead, it produces

(let ([tmp5]
[other6])
(let ([tmp_1tmp])
(set! tmpother)
(set! othertmp_1))
(list tmpother))

with the correct result in (65). Similarly, in the example

(let ([set! 5]
[other6])
(swapset! other)
(list set! other))

the expansion is

(let ([set!_15]
[other6])
(let ([tmpset!_1])
(set! set!_1other)
(set! othertmp))
(list set!_1other))

so that the local set! binding doesn’t interfere with the assignments introduced by the macro template.

In other words, Racket’s pattern-based macros automatically maintain lexical scope, so macro implementors can reason about variable reference in macros and macro uses in the same way as for functions and function calls.

16.1.3define-syntax and syntax-rules πŸ”— i

The define-syntax-rule form binds a macro that matches a single pattern, but Racket’s macro system supports transformers that match multiple patterns starting with the same identifier. To write such macros, the programmer must use the more general define-syntax form along with the syntax-rules transformer form:

(syntax-rules (literal-id...)
[patterntemplate]
...))

The define-syntax-rule form is itself a macro that expands into define-syntax with a syntax-rules form that contains only one pattern and template.

For example, suppose we would like a rotate macro that generalizes swap to work on either two or three identifiers, so that

(let ([red1][green2][blue3])
(rotateredgreen);swaps
(rotateredgreenblue);rotates left
(list redgreenblue))

produces (132). We can implement rotate using syntax-rules :

(define-syntax rotate
[(rotateab)(swapab)]
[(rotateabc)(begin
(swapab)
(swapbc))]))

The expression (rotateredgreen) matches the first pattern in the syntax-rules form, so it expands to (swapredgreen). The expression (rotateredgreenblue) matches the second pattern, so it expands to (begin (swapredgreen)(swapgreenblue)).

16.1.4Matching SequencesπŸ”— i

A better rotate macro would allow any number of identifiers, instead of just two or three. To match a use of rotate with any number of identifiers, we need a pattern form that has something like a Kleene star. In a Racket macro pattern, a star is written as ... .

To implement rotate with ... , we need a base case to handle a single identifier, and an inductive case to handle more than one identifier:

(define-syntax rotate
[(rotatea)(void )]
[(rotateabc... )(begin
(swapab)
(rotatebc... ))]))

When a pattern variable like c is followed by ... in a pattern, then it must be followed by ... in a template, too. The pattern variable effectively matches a sequence of zero or more forms, and it is replaced in the template by the same sequence.

Both versions of rotate so far are a bit inefficient, since pairwise swapping keeps moving the value from the first variable into every variable in the sequence until it arrives at the last one. A more efficient rotate would move the first value directly to the last variable. We can use ... patterns to implement the more efficient variant using a helper macro:

(define-syntax rotate
[(rotateac... )
(shift-to(c... a)(ac... ))]))
(define-syntax shift-to
[(shift-to(from0from... )(to0to... ))
(let ([tmpfrom0])
(set! tofrom)...
(set! to0tmp))]))

In the shift-to macro, ... in the template follows (set! tofrom), which causes the (set! tofrom) expression to be duplicated as many times as necessary to use each identifier matched in the to and from sequences. (The number of to and from matches must be the same, otherwise the macro expansion fails with an error.)

16.1.5Identifier MacrosπŸ”— i

Given our macro definitions, the swap or rotate identifiers must be used after an open parenthesis, otherwise a syntax error is reported:

> (+ swap3)

eval:2:0: swap: bad syntax

in: swap

An identifier macro is a pattern-matching macro that works when used by itself without parentheses. For example, we can define val as an identifier macro that expands to (get-val), so (+ val3) would expand to (+ (get-val)3).

(lambda (stx)
(syntax-case stx()
[val(identifier? (syntax val))(syntax (get-val))])))
> (define-values (get-valput-val!)
(let ([private-val0])
(values (lambda ()private-val)
(lambda (v)(set! private-valv)))))
> val

0

> (+ val3)

3

The val macro uses syntax-case , which enables defining more powerful macros and will be explained in the Mixing Patterns and Expressions: syntax-case section. For now it is sufficient to know that to define a macro, syntax-case is used in a lambda , and its templates must be wrapped with an explicit syntax constructor. Finally, syntax-case clauses may specify additional guard conditions after the pattern.

Our val macro uses an identifier? condition to ensure that val must not be used with parentheses. Instead, the macro raises a syntax error:

> (val)

eval:8:0: val: bad syntax

in: (val)

16.1.6set! TransformersπŸ”— i

With the above val macro, we still must call put-val! to change the stored value. It would be more convenient, however, to use set! directly on val. To invoke the macro when val is used with set! , we create an assignment transformer with make-set!-transformer . We must also declare set! as a literal in the syntax-case literal list.

> (define-syntax val2
(lambda (stx)
[val2(identifier? (syntax val2))(syntax (get-val))]
[(set! val2e)(syntax (put-val!e))]))))
> val2

0

> (+ val23)

3

> (set! val210)
> val2

10

16.1.7Macro-Generating MacrosπŸ”— i

Suppose that we have many identifiers like val and val2 that we’d like to redirect to accessor and mutator functions like get-val and put-val!. We’d like to be able to just write:

(define-get/put-idvalget-valput-val!)

Naturally, we can implement define-get/put-id as a macro:

> (define-syntax-rule (define-get/put-ididgetput!)
(lambda (stx)
[id(identifier? (syntax id))(syntax (get))]
[(set! ide)(syntax (put!e))])))))
> (define-get/put-idval3get-valput-val!)
> (set! val311)
> val3

11

The define-get/put-id macro is a macro-generating macro.

16.1.8Extended Example: Call-by-Reference FunctionsπŸ”— i

We can use pattern-matching macros to add a form to Racket for defining first-order call-by-reference functions. When a call-by-reference function body mutates its formal argument, the mutation applies to variables that are supplied as actual arguments in a call to the function.

For example, if define-cbr is like define except that it defines a call-by-reference function, then

(define-cbr(fab)
(swapab))
(let ([x1][y2])
(fxy)
(list xy))

produces (21).

We will implement call-by-reference functions by having function calls supply accessor and mutators for the arguments, instead of supplying argument values directly. In particular, for the function f above, we’ll generate

(define (do-fget-aget-bput-a!put-b!)
(define-get/put-idaget-aput-a!)
(define-get/put-idbget-bput-b!)
(swapab))

and redirect a function call (fxy) to

(do-f(lambda ()x)
(lambda ()y)
(lambda (v)(set! xv))
(lambda (v)(set! yv)))

Clearly, then define-cbr is a macro-generating macro, which binds f to a macro that expands to a call of do-f. That is, (define-cbr(fab)(swapab)) needs to generate the definition

[(idactual... )
(do-f(lambda ()actual)
...
(lambda (v)
(set! actualv))
... )]))

At the same time, define-cbr needs to define do-f using the body of f, this second part is slightly more complex, so we defer most of it to a define-for-cbr helper module, which lets us write define-cbr easily enough:

(define-syntax-rule (define-cbr(idarg... )body)
[(idactual(... ... ))
(do-f(lambda ()actual)
(... ... )
(lambda (v)
(set! actualv))
(... ... ))]))
(define-for-cbrdo-f(arg... )
();explained below...
body)))

Our remaining task is to define define-for-cbr so that it converts

(define-for-cbrdo-f(ab)()(swapab))

to the function definition do-f above. Most of the work is generating a define-get/put-id declaration for each argument, a and b, and putting them before the body. Normally, that’s an easy task for ... in a pattern and template, but this time there’s a catch: we need to generate the names get-a and put-a! as well as get-b and put-b!, and the pattern language provides no way to synthesize identifiers based on existing identifiers.

As it turns out, lexical scope gives us a way around this problem. The trick is to iterate expansions of define-for-cbr once for each argument in the function, and that’s why define-for-cbr starts with an apparently useless () after the argument list. We need to keep track of all the arguments seen so far and the get and put names generated for each, in addition to the arguments left to process. After we’ve processed all the identifiers, then we have all the names we need.

Here is the definition of define-for-cbr:

(define-syntax define-for-cbr
[(define-for-cbrdo-f(id0id... )
(gens... )body)
(define-for-cbrdo-f(id... )
(gens... (id0getput))body)]
[(define-for-cbrdo-f()
((idgetput)... )body)
(define (do-fget... put... )
(define-get/put-ididgetput)...
body)]))

Step-by-step, expansion proceeds as follows:

(define-for-cbrdo-f(ab)
()(swapab))
=>(define-for-cbrdo-f(b)
([aget_1put_1])(swapab))
=>(define-for-cbrdo-f()
([aget_1put_1][bget_2put_2])(swapab))
=>(define (do-fget_1get_2put_1put_2)
(define-get/put-idaget_1put_1)
(define-get/put-idbget_2put_2)
(swapab))

The “subscripts” on get_1, get_2, put_1, and put_2 are inserted by the macro expander to preserve lexical scope, since the get generated by each iteration of define-for-cbr should not bind the get generated by a different iteration. In other words, we are essentially tricking the macro expander into generating fresh names for us, but the technique illustrates some of the surprising power of pattern-based macros with automatic lexical scope.

The last expression eventually expands to just

(define (do-fget_1get_2put_1put_2)
(let ([tmp(get_1)])
(put_1(get_2))
(put_2tmp)))

which implements the call-by-name function f.

To summarize, then, we can add call-by-reference functions to Racket with just three small pattern-based macros: define-cbr, define-for-cbr, and define-get/put-id.

top
up

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /