manpagez: man pages & more
info guile
Home | html | info | man
[ << ] [ < ] [ 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.

© manpagez.com 2000-2024
Individual documents may contain additional copyright information.