scheme-faq-macros


Scheme Frequently Asked Questions

The material on this page is licensed under the terms of the GNU Free Documentation License. See scheme-faq-license for more information about this.

general | language | macros | misc | programming | standards

Macros

What are "hygienic" macros?

The term "hygienic" has been used in connection with macros since the mid 1980s, if not earlier. Broadly speaking, hygienic macros are macros whose expansion is guaranteed to not cause collisions with definitions already existing in the surrounding environment. Kent Dybvig in [Writing Hygienic Macros in Scheme with Syntax-Case] defines macro hygiene as

follows:

If a macro transformer inserts a binding for an identifier, the new binding will not capture other identifiers of the same name introduced elsewhere.

He also defines referential transparency with respect to macros as

If a macro transformer inserts a free reference to an identifier, the reference refers to the binding that was visible where the transformer was specified, regardless of any local bindings that may surround the use of the macro.

In practice, the term "hygienic macros" usually refers to macros that are both hygienic and referentially transparent according to the above definitions.

Hygienic macros were an optional part of the Scheme standard in revision 4 and became mandatory in revision 5 (i.e. R5RS). Writing hygienic macros in R5RS looks deceptively simple and, in many cases, is deceptively simple. This is due to the fact that the pattern and template syntax used by R5RS macros is very intuitive and many macros therefore can be written just with this intuitive understanding and without having to consider how exactly the macro translation works.

Excellent introductions to the syntax-rules macro system defined by R5RS can be found at http://people.csail.mit.edu/people/jhbrown/scheme/macroslides04.pdf, http://petrofsky.org/src/primer.txt, http://blog.willdonnelly.net/2008/09/04/a-scheme-syntax-rules-primer/.

Is there a portable implementation of hygienic macros?

Kent Dybvig and Oscar Waddell have written a highly portable implementation of R5RS macros that only requires very few hooks into a Scheme implementation in order to work. It can be found at http://www.cs.indiana.edu/chezscheme/syntax-case/. In addition to the standard R5RS macro mechanics, the implementation provides expansion capabilities that go significantly beyond what is defined in R5RS.

SLIB (see here) contains several macro packages, some of which are hygienic.

Is there a way to "loop" in a macro definition?

Yes, by doing it the "Scheme way", i.e. calling the macro recursively. There are examples of this in the Section 7.3 of R5RS. Sometimes results may accrue in the opposite order of what you need, in which case you need to write an auxiliary (sub-)macro along the lines of:

  
 (define-syntax reverse-order 
   (syntax-rules () 
     ((_ e) (reverse-order e ())) 
     ((_ (e . rest) r) (reverse-order rest (e . r))) 
     ((_ () r) r))) 
 (reverse-order (2 3 -)) ;=> 1 
          

What is the correct definition of letrec as a macro?

There is a bug in the R5RS sample implementation of letrec: It expands into an error if there are internal definitions in the body of the letrec. Petrofsky offers the following fixed and more concise definition exploiting internal defines:

  
 (define-syntax letrec 
   (syntax-rules () 
     ((_ ((var init) ...) . body) 
      (let () 
        (define var init) 
        ... 
        (let () . body))))) 
          

Note that the use of define in the above does not result in a circular macro definition. R5RS states that internal defines are equivalent to some suitable letrec expression, but it is not actually possible to define define as an R5RS macro. Hence define must be regarded as primitive rather than derived syntax. Still, there are some Scheme implementations which only provide a primitive top-leveldefine and implement internal defines in terms of letrecs as part of a low-level macro expansion process that gets applied to all expressions. In such implementations the following, more complex, define-free implementation of letrec can be used:

  
 (define-syntax letrec 
   (syntax-rules () 
     ((_ ((var init) ...) . body) 
      (let ((var 'undefined) ...) 
        (let ((var (let ((temp init)) (lambda () (set! var temp)))) 
              ... 
              (bod (lambda () . body))) 
          (var) ... (bod)))))) 
          

What effect do internal definitions have on macro expansion?

Section 5.3. of R5RS states

            Although macros may expand into definitions and syntax
            definitions in any context that permits them, it is an
            error for a definition or syntax definition to shadow a
            syntactic keyword whose meaning is needed to determine
            whether some form in the group of forms that contains the
            shadowing definition is in fact a definition, or, for
            internal definitions, is needed to determine the boundary
            between the group and the expressions that follow the
            group.
          

A common interpretation of this cryptic sentence in section includes that an internal definition cannot affect the expansion of an internal definition in the same sequence of definitions. It is debatable whether such an interpretation is accurate. A typical example of the rule being broken would be:

  
 (define-syntax foo 
   (syntax-rules (X) 
     ((foo X y) (define y 1)) 
     ((foo any y) (define y 2)))) 
  
 (let () 
   (foo X a) 
   (define X 'local) 
   a) 
          

The above let expression is an error because it includes an internal definition that shadows an identifier which impacts the expansion of the macro. Most R5RS implementations will in fact accept the above and return 1 or 2 depending on the order of the two statements inside the let.

How are ellipses (...) "counted" during macro expansion?

Section 4.3.2 of R5RS states

Pattern variables that occur in subpatterns followed by one or more instances of the identifier ... are allowed only in subtemplates that are followed by as many instances of .... They are replaced in the output by all of the subforms they match in the input, distributed as indicated. It is an error if the output cannot be built up as specified.

It is important to understand what is meant by "followed" in the above. Take for example the pattern

  
 (a (b c ...) (d e ...) ...) 
          

What matters is how many ellipsis structurally follow a variable rather than lexically. The variables a, b, c, d and e are followed by three, three, three, two and two ellipses respectively, but structurally they are followed by none, none, one, one and two ellipses respectively. The difference between the structural and lexical ellipsis counts arises because ellipses only apply to the pattern/template immediatetly preceeding them. Thus, the above pattern can supply the input for the template

  
 ((a c ...) (b d ...) (e ...) ...) 
          

The structural ellipsis count of the template variables matches that of the pattern, whereas the lexical template count is completely different.

One issue that is not addressed by the R5RS explanation of macro expansion is how ellipses templates are expanded when they combine variables that matched input of different size. The canonical example for this is

  
 (define-syntax foo 
   (syntax-rules () 
     ((foo (a ...) (b ...)) '((a b) ...)))) 
 (foo (1 2) (3 4 5)) ;=> ? 
          

In different Schemes this is either an error or produces the result '((1 3) (2 4)), i.e. the "oversized" matches are automatically shortened as needed.

Some Schemes relax the above "matching ellipses count" requirement by allowing template variables to be followed by at least as many ellipses as their corresponding pattern variables. The resulting expansion of such "mixed rank ellipses" repeats the matched input:

  
 (define-syntax foo 
   (syntax-rules () 
     ((foo (a ...) (b ...) ...) '(((a b) ...) ...)))) 
 (foo (1 2) (3 4) (5 6 7)) ;=> '(((1 3) (1 4)) ((2 5) (2 6) (2 7))) 
 ;or 
 (foo (1 2) (3 4) (5 6 7)) ;=> '(((1 3) (2 4)) ((1 5) (2 6))) 
          

Note the two alternative expansions; the former is the the more common. For this kind of expansion to work in any ellipses sub-template there must be at least one template variable for which the ellipsis count is exactly the same as in the pattern, otherwise the macro expander would not be able to determine how many times to repeat the matched input of the template variables with ellipsis counts higher than in the pattern.

How do I write a macro that expands into something containing ellipses (...)?

R5RS macros can produce output containing ellipses if there are ellipses in the input, i.e. at the place of macro usage. However, standard R5RS macros cannot introduce ellipses due to their special syntactic status in macro template. Therefore a number of Schemes implement extensions for quoting ellipses. Typically this is done using (... ...). This feature is particularly useful when defining macros that expand into other macro definitions. See here.

Can one write a macro that distinguishes between (foo . (bar)) and (foo bar)?

No. See here for an explanation of the reasons for this. It is however possible to distinguish between genuine proper and improper lists, e.g.

  
 (define-syntax foo 
   (syntax-rules () 
     ((foo (x ...)) 1) 
     ((foo (x . y)) 2))) 
 (foo (1 2 3))     ;=> 1 
 (foo (1 . (2 3))) ;=> 1 
 (foo (1 . 2))     ;=> 2 
          

Is it legal for a macro to expand into a define-syntax expression?

Yes, macros can expand into syntax definitions. Note however that unlike let-syntax and letrec-syntax, define-syntax can only appear at the top level. SRFI-24 extends R5RS syntax to permit the use of define-syntax for internal macro definitions. A further problem is that ellipses in the macro body will be interpreted by the "outer" define-syntax. See here.

Is there a way for a macro to expand into multiple definitions?

Yes, because begin does not create a new scope. Take the following example:

  
 (define-syntax def-multi 
   (syntax-rules () 
     ((def-multi (var ...) expr ...) 
      (begin 
        (define var expr) 
        ...)))) 
 (def-multi (a b) (+ 1 2) (+ 3 4)) 
 (+ a b) ;=> 10 
          

For the specific case of defining multiple variables and binding them to the results of an expression, a number of Schemes include a define-values macro that can be used as follows: (define-values (one two) (values 1 2)). The macro can be defined as follows:

  
 (define-syntax define-values 
   (syntax-rules () 
     ((define-values () exp) 
      (call-with-values (lambda () exp) (lambda () 'unspecified))) 
     ((define-values (var . vars) exp) 
      (begin  
        (define var (call-with-values (lambda () exp) list)) 
        (define-values vars (apply values (cdr var))) 
        (define var (car var)))) 
     ((define-values var exp) 
      (define var (call-with-values (lambda () exp) list))))) 
          

Note that this also handles dotted and empty variable lists, but will generally only work at the top level - support for internal define-values can only be accomplished by implementation-dependent means.

Is there a way to dynamically bind new identifiers?

If your Scheme system supports low-level macros, you can do something like:

  
 (define-macro (foo) 
   `(define ,(construct-name) ...)) 
          

The only way to do this in standard R5RS is using eval. See here for details, but note that this will not work for internal definitions.

Can one write a macro that substitutes occurrences of identifiers?

Standard R5RS macros only expand on the operator (i.e. the first) position in a list datum. You cannot, for instance, define a macro like this:

  
 (define-syntax foo 
   (syntax-rules () 
     (foo (get-foo)))) 
          

Kent Dybvig's macro package (see here), which is also part of Chez Scheme, extends R5RS macro syntax to permit the above. If all you want to do is rename all occurrences of a variable inside a block of code, the following standard R5RS macro does the trick:

  
 (define-syntax let-alias 
   (syntax-rules () 
     ((_ ((id alias) ...) body ...) 
      (let-syntax ((helper (syntax-rules () 
                             ((_ id ...) (begin body ...))))) 
        (helper alias ...))))) 
  
 (define z 1) 
 (let-alias ((x (+ z 1)) 
             (y z)) 
   (set! y x)) 
 z       ;=> 2 
          

Note that, unlike the previous solution, this does not allow a "global" substitution at the top level. The macro also replaces occurences of identifiers in quoted contexts, e.g. in the example 'y would get transformed into 'z, which is not necessarily what the programmer intended. Oleg has implemented a solution in terms of low-level macros that avoids this. Check out http://pobox.com/~oleg/ftp/Scheme/closure-eqv.html.

Is it possible to define sub-macros?

Sub-macros are macros that are only visible within an enclosing macro, i.e. they are the equivalent of internal definitions in Scheme procedures. R5RS offers no means of defining sub-macros. One solution is to get the enclosing macro to emit code containing the sub-macros inside a let-syntax / letrec-syntax. Unfortunately this requires the duplication of the sub-macro code in all the syntax-rule clauses that make use of it.

An alternative solution is to embed all the clauses of the sub-macros in the syntax-rule of the main macro and distinguish them from the main macro clauses and each other by means of secret literals, typically strings. The definitions of do and letrec in R5RS are examples of this technique. The main drawback of this approach is that it becomes hard to distinguish between the several macros encoded in the single syntax-rule. Also the secret literals are not really secret and hence the functionality of the sub-macros is accessible independently from the enclosing macro.

Can one macro call another macro?

Macros can expand into code containing other macros. Expansion continues until the code contains no more macros. Note however that expansion always proceeds head-first. For instance when expanding (macro1 foo (macro2 bar)), macro1 is expanded first and macro2 might never get expanded because the expansion of macro1 might eliminate it or transform it.

As a result of the head-first macro expansion strategy it is generally not possible to write macros that operate on the non-expression contexts of other macros/special forms. It also makes it hard to share functionality between macros since the equivalent of a function call, (a "macro call"), does not exist and hence macros cannot normally use other macros to construct the expansion results. However, macros can be written in a certain way that gives them compositional properties. Details of this approach and code to support it can be found at http://pobox.com/~oleg/ftp/Scheme/macros.html#Macro-CPS-programming

An alternative is to use a combination of Lisp-style procedural macros and invoke macro-expand (or similiar, see here) when calling other macros. However, not all Schemes support these features and procedural macros do not offer the same guarantees of hygiene and referential transparency as provided by R5RS macros.

Is there a way to apply macro transformations to data?

In order to do something like

  
 (transform '(+ 1 2 3))  ;=>  (add (add 1 2) 3) 
          

some Schemes expose the macro expansion logic along the lines of

  
 (macro-expand datum) => expanded-datum 

but this requires the macros to be defined in the program and hence they would also apply to the program itself and not just the data. The easiest way around this is to wrap all data macros in an enclosing macro, e.g.

  
 (macro-expand `(expand-data ,datum)) 
          

where expand-data is the name of the enclosing macro, which is the only macro that needs to be introduced at the top level.

There are some dedicated pattern-matching packages out there, e.g. http://www.cs.indiana.edu/scheme-repository/code.match.html. Bigloo and PLT have built-in pattern matching facilities, and Chez Scheme has a significantly expanded macro expansion capabilities that can operate on both programs and data.

The WebIt! (http://celtic.benderweb.net/webit/) XML processing framework for PLT Scheme includes a facility for transforming XML data using expansion-passing style Scheme macros.


category-scheme-faq