[ << ] | [ < ] | [ 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:
- Scheme Procedure: identifier? syntax-object
Returns
#t
if syntax-object is an identifier, or#f
otherwise.
;; 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 April 20, 2013 using texi2html 5.0.