Meta Definitions
Everyone who has written procedural macros has probably stumbled over phasing issues at least once:
$ cat prog1.scm
(import (scheme base)
(srfi 211 syntax-case))
(define (quote-syntax x)
#`(quote #,(datum->syntax #'* x)))
(define-syntax quote-foo
(lambda (x)
(quote-syntax 'foo)))
$ unsyntax-scheme prog1.scm
prog1.scm: 9.6-17: out of phase identifier ‘quote-syntax’
The reason for this is that the definition of quote-syntax
creates a
runtime binding, which is not established until the program is
executed. The right hand side of the syntax definition of
quote-foo
, however, is already evaluated at expand-time when the
quote-syntax
procedure does not exists yet.
There are a number of solutions. The obvious one is to move the
definition of quote-syntax
to the right hand side of a syntax
definition so that it is already evaluated at expand time:
(import (scheme base)
(srfi 211 syntax-case))
(define-syntax quote-foo
(lambda (x)
(define (quote-syntax x)
#`(quote #,(datum->syntax #'* x)))
(quote-syntax 'foo)))
The disadvantage of this approach (aside from the extra indentation of
quote-syntax
) is that it hardly generalizes if more than one macro
wants to use the helper procedure.
A viable approach is to create a helper library that contains the helper procedure:
(library (quote-syntax)
(export quote-syntax)
(import (scheme base)
(srfi 211 syntax-case))
(define (quote-syntax x)
#`(quote #,(datum->syntax #'* x))))
The top-level program then looks like this:
(import (scheme base)
(quote-syntax))
(define-syntax quote-foo
(lambda (x)
(quote-syntax 'foo)))
This time, we don’t get a phasing error. The reason is that a library can be evaluated already at expand-time of another library or the top-level program. While this is a working solution, it creates the need of an extra helper library, which can be undesirable (especially if it would lead to a chain of dependent helper libraries).
So let’s try again with a one-file solution. We can try to change the
variable definition of quote-syntax
into a syntax definition:
(import (scheme base)
(srfi 211 syntax-case))
(define-syntax quote-syntax
(syntax-rules
((quote-syntax x) #`(quote #,(datum->syntax #'* x)))))
(define-syntax quote-foo
(lambda (x)
(quote-syntax 'foo)))
This works because the syntax binding to quote-syntax
here is
already established at expand-time. It is not an approach that
scales, though, as the quote-syntax
macro will inline its template
everywhere it is used, which becomes unviable when the helper
“procedures” become large.
All solutions presented so far had their flaws or were, comparably,
complicated. So let’s think of a simple solution once more. The
original problem was that the variable binding of quote-syntax
was
established at runtime and not at expand-time. Wouldn’t it be nice if
we could tell our Scheme system that we want this definition to take
place at expand-time and not later?
And, in fact, this is now possible with Unsyntax, which now supports
the meta
keyword possibly known from Chez Scheme. Any form prefixed
with meta becomes a definition, which is evaluated at expand-time
instead of runtime. And so our final solution looks as follows (the
meta
keyword is exported by the (unsyntax)
library):
(import (scheme base)
(srfi 211 syntax-case)
(unsyntax))
(meta define (quote-syntax x)
#`(quote #,(datum->syntax #'* x)))
(define-syntax quote-foo
(lambda (x)
(quote-syntax 'foo)))