| [ << ] | [ < ] | [ Up ] | [ > ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
6.10.3.1 Why syntax-case?
The examples we have shown thus far could just as well have been expressed with
syntax-rules, and have just shown that syntax-case is more
verbose, which is true. But there is a difference: syntax-case creates
procedural macros, giving the full power of Scheme to the macro expander.
This has many practical applications.
A common desire is to be able to match a form only if it is an identifier. This
is impossible with syntax-rules, given the datum matching forms. But with
syntax-case it is easy:
;; relying on previous add1 definition
(define-syntax add1!
(lambda (x)
(syntax-case x ()
((_ var) (identifier? #'var)
#'(set! var (add1 var))))))
(define foo 0)
(add1! foo)
foo ⇒ 1
(add1! "not-an-identifier") ⇒ error
With syntax-rules, the error for (add1! "not-an-identifier") would
be something like “invalid set!”. With syntax-case, it will say
something like “invalid add1!”, because we attach the guard
clause to the pattern: (identifier? #'var). This becomes more important
with more complicated macros. It is necessary to use identifier?, because
to the expander, an identifier is more than a bare symbol.
Note that even in the guard clause, we reference the var pattern variable
within a syntax form, via #'var.
Another common desire is to introduce bindings into the lexical context of the
output expression. One example would be in the so-called “anaphoric macros”,
like aif. Anaphoric macros bind some expression to a well-known
identifier, often it, within their bodies. For example, in (aif
(foo) (bar it)), it would be bound to the result of (foo).
To begin with, we should mention a solution that doesn’t work:
;; doesn't work
(define-syntax aif
(lambda (x)
(syntax-case x ()
((_ test then else)
#'(let ((it test))
(if it then else))))))
The reason that this doesn’t work is that, by default, the expander will
preserve referential transparency; the then and else expressions
won’t have access to the binding of it.
But they can, if we explicitly introduce a binding via datum->syntax.
- Scheme Procedure: datum->syntax for-syntax datum
Create a syntax object that wraps datum, within the lexical context corresponding to the syntax object for-syntax.
For completeness, we should mention that it is possible to strip the metadata from a syntax object, returning a raw Scheme datum:
- Scheme Procedure: syntax->datum syntax-object
Strip the metadata from syntax-object, returning its contents as a raw Scheme datum.
In this case we want to introduce it in the context of the whole
expression, so we can create a syntax object as (datum->syntax x 'it),
where x is the whole expression, as passed to the transformer procedure.
Here’s another solution that doesn’t work:
;; doesn't work either
(define-syntax aif
(lambda (x)
(syntax-case x ()
((_ test then else)
(let ((it (datum->syntax x 'it)))
#'(let ((it test))
(if it then else)))))))
The reason that this one doesn’t work is that there are really two
environments at work here – the environment of pattern variables, as
bound by syntax-case, and the environment of lexical variables,
as bound by normal Scheme. The outer let form establishes a binding in
the environment of lexical variables, but the inner let form is inside a
syntax form, where only pattern variables will be substituted. Here we
need to introduce a piece of the lexical environment into the pattern
variable environment, and we can do so using syntax-case itself:
;; works, but is obtuse
(define-syntax aif
(lambda (x)
(syntax-case x ()
((_ test then else)
;; invoking syntax-case on the generated
;; syntax object to expose it to `syntax'
(syntax-case (datum->syntax x 'it) ()
(it
#'(let ((it test))
(if it then else))))))))
(aif (getuid) (display it) (display "none")) (newline)
-| 500
However there are easier ways to write this. with-syntax is often
convenient:
- Syntax: with-syntax ((pat val)...) exp...
Bind patterns pat from their corresponding values val, within the lexical context of exp....
;; better (define-syntax aif (lambda (x) (syntax-case x () ((_ test then else) (with-syntax ((it (datum->syntax x 'it))) #'(let ((it test)) (if it then else)))))))
As you might imagine, with-syntax is defined in terms of
syntax-case. But even that might be off-putting to you if you are an old
Lisp macro hacker, used to building macro output with quasiquote. The
issue is that with-syntax creates a separation between the point of
definition of a value and its point of substitution.
So for cases in which a quasiquote style makes more sense,
syntax-case also defines quasisyntax, and the related
unsyntax and unsyntax-splicing, abbreviated by the reader as
#`, #,, and #,@, respectively.
For example, to define a macro that inserts a compile-time timestamp into a source file, one may write:
(define-syntax display-compile-timestamp
(lambda (x)
(syntax-case x ()
((_)
#`(begin
(display "The compile timestamp was: ")
(display #,(current-time))
(newline))))))
Readers interested in further information on syntax-case macros should
see R. Kent Dybvig’s excellent The Scheme Programming Language, either
edition 3 or 4, in the chapter on syntax. Dybvig was the primary author of the
syntax-case system. The book itself is available online at
http://scheme.com/tspl4/.
| [ << ] | [ < ] | [ Up ] | [ > ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated on February 3, 2012 using texi2html 5.0.
