macros chapter in guide

svn: r9927
This commit is contained in:
Matthew Flatt 2008-05-22 16:12:51 +00:00
parent ba895e827f
commit 6a93d0c531
7 changed files with 968 additions and 6 deletions

View File

@ -173,6 +173,7 @@
(cond
[(and v (hash-ref ht v #f))
=> (lambda (v) v)]
[(syntax? v) (make-literal-syntax v)]
[(string? v) (install ht v (string-copy v))]
[(bytes? v) (install ht v (bytes-copy v))]
[(pair? v)

View File

@ -66,7 +66,7 @@
(list
(datum->syntax
#'filename
`(code:comment (unsyntax (t "In \"" ,#'filename "\":")))
`(code:comment (unsyntax (t "In \"" ,(syntax-e #'filename) "\":")))
#'filename))
null)])
(syntax/loc stx (schemeblock file ... modtag rest ...)))]

View File

@ -21,7 +21,8 @@
current-meta-list
(struct-out shaped-parens)
(struct-out just-context))
(struct-out just-context)
(struct-out literal-syntax))
(define no-color "schemeplain")
(define reader-color "schemereader")
@ -121,7 +122,9 @@
(memq (syntax-e c) (current-variable-list)))]
[(s it? sub?)
(let ([sc (syntax-e c)])
(let ([s (format "~s" sc)])
(let ([s (format "~s" (if (literal-syntax? sc)
(literal-syntax-stx sc)
sc))])
(if (and (symbol? sc)
((string-length s) . > . 1)
(char=? (string-ref s 0) #\_)
@ -654,6 +657,7 @@
(define-struct shaped-parens (val shape))
(define-struct just-context (val ctx))
(define-struct literal-syntax (stx))
(define-struct graph-reference (bx))
(define-struct graph-defn (r bx))

View File

@ -3,7 +3,7 @@
scribble/eval
"guide-utils.ss")
@title[#:style 'quiet]{Syntax Certificates}
@title[#:tag "stx-certs" #:style 'quiet]{Syntax Certificates}
A use of a macro can expand into a use of an identifier that is not
exported from the module that binds the macro. In general, such an
@ -18,7 +18,7 @@ expands to a use of @scheme[unchecked-go]:
(module m mzscheme
(provide go)
(define (unchecked-go n x)
;; to avoid disaster, @scheme[n] must be a number
(code:comment #, @t{to avoid disaster, @scheme[n] must be a number})
(+ n 17))
(define-syntax (go stx)
(syntax-case stx ()

View File

@ -5,7 +5,21 @@
@title[#:tag "macros" #:style 'toc]{Macros}
A @deftech{macro} is a syntactic form with an associated
@deftech{transformer} that @deftech{expands} the original form
into existing forms. To put it another way, a macro is an
extension to the Scheme compiler. Most of the syntactic forms of
@schememodname[scheme/base] and @schememodname[scheme] are
actually macros that expand into a small set of core constructs.
Like many languages, Scheme provides pattern-based macros that
make simple transformations easy to implement and reliable to
use. Scheme also supports arbitrary macro transformers that are
implemented in Scheme---or in a macro-extended variant of Scheme.
@local-table-of-contents[]
@;------------------------------------------------------------------------
@include-section["certificates.scrbl"]
@include-section["pattern-macros.scrbl"]
@include-section["proc-macros.scrbl"]

View File

@ -0,0 +1,481 @@
#lang scribble/doc
@(require scribble/manual
scribble/eval
"guide-utils.ss")
@title[#:tag "pattern-macros"]{Pattern-Based Macros}
A @deftech{pattern-based macro} replaces any code that matches a
pattern to an expansion that uses parts of the original syntax that
match parts of the pattern.
@; ----------------------------------------
@section{@scheme[define-syntax-rule]}
The simplest way to create a macro is to use
@scheme[define-syntax-rule]:
@specform[(define-syntax-rule pattern template)]
As a running example, consider the @scheme[swap] macro, which swaps
the values stored in two variables. It can be implemented using
@scheme[define-syntax-rule] as follows:
@margin-note{The macro is ``un-Schemely'' in the sense that it
involves side effects on variables---but the point of macros is to let
you add syntactic forms that some other language designer might not
approve.}
@schemeblock[
(define-syntax-rule (swap x y)
(let ([tmp x])
(set! x y)
(set! y tmp)))
]
The @scheme[define-syntax-rule] form binds a macro that matches a
single pattern. The pattern must always start with an open parenthesis
followed by an identifier, which is @scheme[swap] in this case. After
the initial identifier, other identifiers are @deftech{macro pattern
variables} that can match anything in a use of the macro. Thus, this
macro matches the for @scheme[(swap _form_1 _form_2)] for any
@scheme[_form_1] and @scheme[_form_2].
@margin-note{Macro pattern variables similar to pattern variables for
@scheme[match]. See @secref["match"].}
After the pattern in @scheme[define-syntax-rule] is the
@deftech{template}. The template is used in place of a form that
matches the pattern, except that each instance of a pattern variable
in the template is replaced with the part of the macro use the pattern
variable matched. For example, in
@schemeblock[(swap first last)]
the pattern variable @scheme[x] matches @scheme[first] and @scheme[y]
matches @scheme[last], so that the expansion is
@schemeblock[
(let ([tmp first])
(set! first last)
(set! last tmp))
]
@; ----------------------------------------
@section{Lexical Scope}
Suppose that we use the @scheme[swap] macro to swap variables named
@scheme[tmp] and @scheme[other]:
@schemeblock[
(let ([tmp 5]
[other 6])
(swap tmp other)
(list tmp other))
]
The result of the above expression should be @schemeresult[(6 5)]. The
naive expansion of this use of @scheme[swap], however, is
@schemeblock[
(let ([tmp 5]
[other 6])
(let ([tmp tmp])
(set! tmp other)
(set! other tmp))
(list tmp other))
]
whose result is @schemeresult[(5 6)]. The problem is that the naive
expansion confuses the @scheme[tmp] in the context where @scheme[swap]
is used with the @scheme[tmp] that is in the macro template.
Scheme doesn't produce the naive expansion for the above use of
@scheme[swap]. Instead, it produces
@schemeblock[
(let ([tmp 5]
[other 6])
(let ([tmp_1 tmp])
(set! tmp other)
(set! other tmp_1))
(list tmp other))
]
with the correct result in @schemeresult[(6 5)]. Similarly, in the
example
@schemeblock[
(let ([set! 5]
[other 6])
(swap set! other)
(list set! other))
]
the expansion is
@schemeblock[
(let ([set!_1 5]
[other 6])
(let ([tmp_1 tmp])
(set! set!_1 other)
(set! other tmp_1))
(list set!_1 other))
]
so that the local @scheme[set!] binding doesn't interfere with the
assignments introduced by the macro template.
In other words, Scheme's pattern-based macros automatically maintain
lexical scope, so macro implementors can reason about variable
reference in macros and macro uses in the same way as for functions
and function calls.
@; ----------------------------------------
@section{@scheme[define-syntax] and @scheme[syntax-rules]}
The @scheme[define-syntax-rule] form binds a macro that matches a
single pattern, but Scheme's macro system supports transformers that
match multiple patterns starting with the same identifier. To write
such macros, the programmer much use the more general
@scheme[define-syntax] form along with the @scheme[syntax-rules]
transformer form:
@specform[(define-syntax id
(syntax-rules (literal-id ...)
[pattern template]
...))]
@margin-note{The @scheme[define-syntax-rule] form is itself a macro
that expands into @scheme[define-syntax] with a @scheme[syntax-rules]
form that contains only one pattern and template.}
For example, suppose we would like a @scheme[rotate] macro that
generalizes @scheme[swap] to work on either two or three identifiers,
so that
@schemeblock[
(let ([red 1] [green 2] [blue 3])
(rotate red green) (code:comment #, @t{swaps})
(rotate red green blue) (code:comment #, @t{rotates left})
(list red green blue))
]
produces @schemeresult[(1 3 2)]. We can implement @scheme[rotate]
using @scheme[syntax-rules]:
@schemeblock[
(define-syntax rotate
(syntax-rules ()
[(rotate a b) (swap a b)]
[(rotate a b c) (begin
(swap a b)
(swap b c))]))
]
The expression @scheme[(rotate red green)] matches the first pattern
in the @scheme[syntax-rules] form, so it expands to @scheme[(swap red
green)]. The expression @scheme[(rotate a b c)] matches the second
pattern, so it expands to @scheme[(begin (swap red green) (swap green
blue))].
@; ----------------------------------------
@section{Matching Sequences}
A better @scheme[rotate] macro would allow any number of identifiers,
instead of just two or three. To match a use of @scheme[rotate] with
any number of identifiers, we need a pattern form that has something
like a Kleene star. In a Scheme macro pattern, a star is written as
@scheme[...].
To implement @scheme[rotate] with @scheme[...], we need a base case to
handle a single identifier, and an inductive case to handle more than
one identifier:
@schemeblock[
(define-syntax rotate
(syntax-rules ()
[(rotate a) (void)]
[(rotate a b c ...) (begin
(swap a b)
(rotate b c ...))]))
]
When a pattern variable like @scheme[c] is followed by @scheme[...] in
a pattern, then it must be followed by @scheme[...] in a template,
too. The pattern variable effectively matches a sequence of zero or
more forms, and it is replaced in the template by the same sequence.
Both versions of @scheme[rotate] so far are a bit inefficient, since
pairwise swapping keeps moving the value from the first variable into
every variable in the sequence until it arrives at the last one. A
more efficient @scheme[rotate] would move the first value directly to
the last variable. We can use @scheme[...] patterns to implement the
more efficient variant using a helper macro:
@schemeblock[
(define-syntax rotate
(syntax-rules ()
[(rotate a c ...)
(shift-to (c ... a) (a c ...))]))
(define-syntax shift-to
(syntax-rules ()
[(shift-to (from0 from ...) (to0 to ...))
(let ([tmp from0])
(set! to from) ...
(set! to0 tmp))]))
]
In the @scheme[shift-to] macro, @scheme[...] in the template follows
@scheme[(set! to from)], which causes the @scheme[(set! to from)]
expression to be duplicated as many times as necessary to use each
identifier matched in the @scheme[to] and @scheme[from]
sequences. (The number of @scheme[to] and @scheme[from] matches must
be the same, otherwise the macro expansion fails with an error.)
@; ----------------------------------------
@section{Identifier Macros}
Given our macro definitions, the @scheme[swap] or @scheme[rotate]
identifiers must be used after an open parenthesis, otherwise a syntax
error is reported:
@interaction-eval[(define-syntax swap (syntax-rules ()))]
@interaction[(+ swap 3)]
An @deftech{identifier macro} works in any expression. For example, we
can define @scheme[clock] as an identifier macro that expands to
@scheme[(get-clock)], so @scheme[(+ clock 3)] would expand to
@scheme[(+ (get-clock) 3)]. An identifier macro also cooperates with
@scheme[set!], and we can define @scheme[clock] so that @scheme[(set!
clock 3)] expands to @scheme[(put-clock! 3)].
The @scheme[syntax-id-rules] form is like @scheme[syntax-rules], but
it creates a transformer that acts as an identifier macro:
@specform[(define-syntax id
(syntax-id-rules (literal-id ...)
[pattern template]
...))]
Unlike a @scheme[syntax-rules] form, the @scheme[_pattern]s are not
required to start with an open parenthesis. Also, @scheme[set!] is
typically used as a literal to match a use of @scheme[set!] in the
pattern (as opposed to being a pattern variable.
@schemeblock[
(define-syntax clock
(syntax-id-rules (set!)
[(set! cock e) (put-clock! e)]
[(clock a ...) ((get-clock) a ...)]
[clock (get-clock)]))
]
The @scheme[(clock a ...)] pattern is needed because, when an
identifier macro is used after an open parenthesis, the macro
transformer is given the whole form, like with a non-identifier macro.
Put another way, the @scheme[syntax-rules] form is essentially a
special case of the @scheme[syntax-id-rules] form with errors in the
@scheme[set!] and lone-identifier cases.
@; ----------------------------------------
@section{Macro-Generating Macros}
Suppose that we have many identifier like @scheme[clock] that we'd
like to redirect to accessor and mutator functions like
@scheme[get-clock] and @scheme[put-clock!]. We'd like to be able to
just write
@schemeblock[
(define-get/put-id clock get-clock put-clock!)
]
Naturally, we can implement @scheme[define-get/put-id] as a macro:
@schemeblock[
(define-syntax-rule (define-get/put-id id get put!)
(define-syntax clock
(syntax-id-rules (set!)
[(set! cock e) (put-clock! e)]
[(clock a (... ...)) ((get-clock) a (... ...))]
[clock (get-clock)])))
]
The @scheme[define-get/put-id] macro is a @deftech{macro-generating
macro}. The only non-obvious part of its definition is the
@scheme[(... ...)], which ``quotes'' @scheme[...] so that it takes its
usual role in the generated macro, instead of the generating macro.
@; ----------------------------------------
@section[#:tag "pattern-macro-example"]{Extended Example: Call-by-Reference Functions}
We can use pattern-matching macros to implement add a form to Scheme
for defining first-order @deftech{call-by-reference} functions. When a
call-by-reference function body mutates its formal argument, the
mutation applies to variables that are supplied as actual arguments in
a call to the function.
For example, if @scheme[define-cbr] is like @scheme[define] except
that it defines a call-by-reference function, then
@schemeblock[
(define-cbr (f a b)
(swap a b))
(let ([x 1] [y 2])
(f x y)
(list x y))
]
produces @schemeresult[(2 1)].
We will implement call-by-reference functions by having function calls
supply accessor and mutators for the arguments, instead of supplying
argument values directly. In particular, for the function @scheme[f]
above, we'll generate
@schemeblock[
(define (do-f get-a get-b put-a! put-b!)
(define-get/put-id a get-a put-a!)
(define-get/put-id b get-b put-b!)
(swap a b))
]
and redirect a function call @scheme[(f x y)] to
@schemeblock[
(do-f (lambda () x)
(lambda () y)
(lambda (v) (set! x v))
(lambda (v) (set! y v)))
]
Clearly, then @scheme[define-cbr] is a macro-generating macro, which
binds @scheme[f] to a macro that expands to a call of @scheme[do-f].
That is, @scheme[(define-cbr (f a b) (swap ab))] needs to generate the
definition
@schemeblock[
(define-syntax f
(syntax-rules ()
[(id actual ...)
(do-f (lambda () actual)
...
(lambda (v)
(set! actual v))
...)]))
]
At the same time, @scheme[define-cbr] needs to define @scheme[do-f]
using the body of @scheme[f], this second part is slightly more
complex, so we defer most it to a @scheme[define-for-cbr] helper
module, which lets us write @scheme[define-cbr] easily enough:
@schemeblock[
(define-syntax-rule (define-cbr (id arg ...) body)
(begin
(define-syntax id
(syntax-rules ()
[(id actual (... ...))
(do-f (lambda () actual)
(... ...)
(lambda (v)
(set! actual v))
(... ...))]))
(define-for-cbr do-f (arg ...)
() (code:comment #, @t{explained below...})
body)))
]
Our remaining task is to define @scheme[define-for-cbr] so that it
converts
@schemeblock[
(define-for-cbr do-f (a b) () (swap a b))
]
to the function definition @scheme[do-f] above. Most of the work is
generating a @scheme[define-get/put-id] declaration for each argument,
@scheme[a] ad @scheme[b], and putting them before the body. Normally,
that's an easy task for @scheme[...] in a pattern and template, but
this time there's a catch: we need to generate the names
@scheme[get-a] and @scheme[put-a!] as well as @scheme[get-b] and
@scheme[put-b!], and the pattern language provides no way to
synthesize identifiers based on existing identifiers.
As it turns out, lexical scope gives us a way around this problem. The
trick is to iterate expansions of @scheme[define-for-cbr] once for
each argument in the function, and that's why @scheme[define-cbr]
starts with an apparently useless @scheme[()] after the argument
list. We need to keep track of all the arguments seen so far and the
@scheme[get] and @scheme[put] names generated for each, in addition to
the arguments left to process. After we've processed all the
identifiers, then we have all the names we need.
Here is the definition of @scheme[define-for-cbr]:
@schemeblock[
(define-syntax define-for-cbr
(syntax-rules ()
[(define-for-cbr do-f (id0 id ...)
(gens ...) body)
(define-for-cbr do-f (id ...)
(gens ... (id0 get put)) body)]
[(define-for-cbr do-f ()
((id get put) ...) body)
(define (do-f get ... put ...)
(define-get/put-id id get put) ...
body)]))
]
Step-by-step, expansion proceeds as follows:
@schemeblock[
(define-for-cbr do-f (a b)
() (swap a b))
=> (define-for-cbr do-f (b)
([a get_1 put_1]) (swap a b))
=> (define-for-cbr do-f ()
([a get_1 put_1] [b get_2 put_2]) (swap a b))
=> (define (do-f get_1 get_2 put_1 put_2)
(define-get/put-id a get_1 put_1)
(define-get/put-id b get_2 put_2)
(swap a b))
]
The ``subscripts'' on @scheme[get_1], @scheme[get_2],
@scheme[put_1], and @scheme[put_2] are inserted by the macro
expander to preserve lexical scope, since the @scheme[get]
generated by each iteration of @scheme[define-for-cbr] should not
bind the @scheme[get] generated by a different iteration. In
other words, we are essentially tricking the macro expander into
generating fresh names for us, but the technique illustrates some
of the surprising power of pattern-based macros with automatic
lexical scope.
The last expression eventually expands to just
@schemeblock[
(define (do-f get_1 get_2 put_1 put_2)
(let ([tmp (get_1)])
(put_1 (get_2))
(put_2 tmp)))
]
which implements the call-by-name function @scheme[f].
To summarize, then, we can add call-by-reference functions to
Scheme with just three small pattern-based macros:
@scheme[define-cbr], @scheme[define-for-cbr], and
@scheme[define-get/put-id].

View File

@ -0,0 +1,462 @@
#lang scribble/doc
@(require scribble/manual
scribble/eval
"guide-utils.ss")
@(define check-eval (make-base-eval))
@(interaction-eval #:eval check-eval (require (for-syntax scheme/base)))
@(define-syntax-rule (schemeblock/eval #:eval e body ...)
(begin
(interaction-eval #:eval e body) ...
(schemeblock body ...)))
@title[#:tag "proc-macros" #:style 'toc]{General Macro Transformers}
The @scheme[define-syntax] form creates a @deftech{transformer
binding} for an identifier, which is a binding that can be used at
compile time while expanding expressions to be evaluated at run time.
The compile-time value associated with a transformer binding can be
anything; if it is a procedure of one argument, then the binding is
used as a macro, and the procedure is the @deftech{macro transformer}.
The @scheme[syntax-rules] and @scheme[syntax-id-rules] forms are
macros that expand to procedure forms. For example, if you evaluate a
@scheme[syntax-rules] form directly (instead of placing on the
right-hand of a @scheme[define-syntax] form), the result is a
procedure:
@interaction[
(syntax-rules () [(nothing) something])
]
Instead of using @scheme[syntax-rules], you can write your own macro
transformer procedure directly using @scheme[lambda]. The argument to
the procedure is a values that represents the source form, and the
result of the procedure must be a value that represents the
replacement form.
@local-table-of-contents[]
@; ----------------------------------------
@section[#:tag "stx-obj"]{Syntax Objects}
The input and output of a macro transformer (i.e., source and
replacement forms) are represented as @deftech{syntax objects}. A
syntax object contains symbols, lists, and constant values (such as
numbers) that essentially correspond to the @scheme[quote]d form of
the expression. For example, a representation of the expression
@scheme[(+ 1 2)] contains the symbol @scheme['+] and the numbers
@scheme[1] and @scheme[2], all in a list. In addition to this quoted
content, a syntax object associates source-location and
lexical-binding information with each part of the form. The
source-location information is used when reporting syntax errors (for
example), and the lexical-biding information allows the macro system
to maintain lexical scope. To accommodate this extra information, the
represention of the expression @scheme[(+ 1 2)] is not merely
@scheme['(+ 1 2)], but a packaging of @scheme['(+ 1 2)] into a syntax
object.
To create a literal syntax object, use the @scheme[syntax] form:
@interaction[
(eval:alts (#,(scheme syntax) (+ 1 2)) (syntax (+ 1 2)))
]
In the same way that @litchar{'} abbreviates @scheme[quote],
@litchar{#'} abbreviates @scheme[syntax]:
@interaction[
#'(+ 1 2)
]
A syntax object that contains just a symbol is an @deftech{identifier
syntax object}. Scheme provides some additional operations specific to
identifier syntax objects, including the @scheme[identifier?]
operation to detect identifiers. Most notably,
@scheme[free-identifier=?] determines whether two identifiers refer
to the same binding:
@interaction[
(identifier? #'car)
(identifier? #'(+ 1 2))
(free-identifier=? #'car #'cdr)
(free-identifier=? #'car #'car)
(require (only-in scheme/base [car also-car]))
(free-identifier=? #'car #'also-car)
(free-identifier=? #'car (let ([car 8])
#'car))
]
The last example above, in particular, illustrates how syntax objects
preserve lexical-context information.
To see the lists, symbols, numbers, @|etc| within a syntax object, use
@scheme[syntax->datum]:
@interaction[
(syntax->datum #'(+ 1 2))
]
The @scheme[syntax-e] function is similar to @scheme[syntax->datum],
but it unwraps a single layer of source-location and lexical-context
information, leaving sub-forms that have their own information wrapped
as syntax objects:
@interaction[
(syntax-e #'(+ 1 2))
]
The @scheme[syntax-e] function always leaves syntax-object wrappers
around sub-forms that are represented via symbols, numbers, and other
literal values. The only time it unwraps extra sub-forms is when
unwrapping a pair, in which case the @scheme[cdr] of the pair may be
recursively unwrapped, depending on how the syntax object was
constructed.
The oppose of @scheme[syntax->datum] is, of course,
@scheme[datum->syntax]. In addition to a datum like @scheme['(+ 1
2)], @scheme[datum->syntax] needs an existing syntax object to donate
its lexical context, and optionally another syntax object to donate
its source location:
@interaction[
(datum->syntax #'lex
'(+ 1 2)
#'srcloc)
]
In the above example, the lexical context of @scheme[#'lex] is used
for the new syntax object, while the source location of
@scheme[#'srcloc] is used.
When the second (i.e., the ``datum'') argument to
@scheme[datum->syntax] includes syntax objects, those syntax objects
are preserved intact in the result. That is, deconstructing the result
with @scheme[syntax-e] eventually produces the syntax objects that
were given to @scheme[datum->syntax].
@; ----------------------------------------
@section[#:tag "syntax-case"]{Mixing Patterns and Expressions: @scheme[syntax-case]}
The procedure generated by @scheme[syntax-rules] internally uses
@scheme[syntax-e] to deconstruct the given syntax object, and it uses
@scheme[datum->syntax] to construct the result. The
@scheme[syntax-rules] form doesn't provide a way to escape from
pattern-matching and template-construction mode into an arbitrary
Scheme expression.
The @scheme[syntax-case] form lets you mix pattern matching, template
construction, and arbitrary expressions:
@specform[(syntax-case stx-expr (literal-id ...)
[pattern expr]
...)]
Unlike @scheme[syntax-rules], the @scheme[syntax-case] form does not
produce a procedure. Instead, it starts with a @scheme[_stx-expr]
expression that determines the syntax object to match against the
@scheme[_pattern]s. Also, each @scheme[syntax-case] clause has a
@scheme[_pattern] and @scheme[_expr], instead of a @scheme[_pattern]
and @scheme[_template]. Within an @scheme[_expr], the @scheme[syntax]
form---usually abbreviated with @litchar{#'}---shifts into
template-construction mode; if the @scheme[_expr] of a clause starts
with @litchar{#'}, then we have something like a @scheme[syntax-rules]
form:
@interaction[
(syntax->datum
(syntax-case #'(+ 1 2) ()
[(op n1 n2) #'(- n1 n2)]))
]
We could write the @scheme[swap] macro using @scheme[syntax-case]
instead of @scheme[define-syntax-rule] or @scheme[syntax-rules]:
@schemeblock[
(define-syntax swap
(lambda (stx)
(syntax-case stx ()
[(swap x y) #'(let ([tmp x])
(set! x y)
(set! y tmp))])))
]
One advantage of using @scheme[syntax-case] is that we can provide
better error reporting for @scheme[swap]. For example, with the
@scheme[define-syntax-rule] definition of @scheme[swap], then
@scheme[(swap x 2)] produces a syntax error in terms of @scheme[set!],
because @scheme[2] is not an identifier. We can refine our
@scheme[syntax-case] implementation of @scheme[swap] to explicitly
check the sub-forms:
@schemeblock[
(define-syntax swap
(lambda (stx)
(syntax-case stx ()
[(swap x y)
(if (and (identifier? #'x)
(identifier? #'y))
#'(let ([tmp x])
(set! x y)
(set! y tmp))
(raise-syntax-error #f
"not an identifier"
stx
(if (identifier? #'x)
#'y
#'x)))])))
]
With this definition, @scheme[(swap x 2)] provides a syntax error
originating from @scheme[swap] instead of @scheme[set!].
In the above definition of @scheme[swap], @scheme[#'x] and
@scheme[#'y] are templates, even though they are not used as the
result of the macro transformer. This example illustrates how
templates can be used to access pieces of the input syntax, in this
case for checking the form of the pieces. Also, the match for
@scheme[#'x] or @scheme[#'y] is used in the call to
@scheme[raise-syntax-error], so that the syntax-error message can
point directly to the source location of the non-identifier.
@; ----------------------------------------
@section[#:tag "with-syntax"]{@scheme[with-syntax] and @scheme[generate-temporaries]}
Since @scheme[syntax-case] lets us compute with arbitrary Scheme
expression, we can more simply solve a problem that we had in
writing @scheme[define-for-cbr] (see
@secref["pattern-macro-example"]), where we needed to generate a
set of names based on a sequence @scheme[id ...]:
@schemeblock[
(define-syntax (define-for-cbr stx)
(syntax-case stx ()
[(_ do-f (id ...) body)
....
#'(define (do-f get ... put ...)
(define-get/put-id id get put) ...
body) ....]))
]
@margin-note{This example uses @scheme[(define-syntax (_id _arg) _body ...+)],
which is equivalent to @scheme[(define-syntax _id (lambda (_arg) _body ...+))].}
In place of the @scheme[....]s above, we need to bind @scheme[get
...] and @scheme[put ...] to lists of generated identifiers. We
cannot use @scheme[let] to bind @scheme[get] and @scheme[put],
because we need bindings that count as pattern variables, instead
of normal local variables. The @scheme[with-syntax] form lets us
bind pattern variables:
@schemeblock[
(define-syntax (define-for-cbr stx)
(syntax-case stx ()
[(_ do-f (id ...) body)
(with-syntax ([(get ...) ....]
[(put ...) ....])
#'(define (do-f get ... put ...)
(define-get/put-id id get put) ...
body))]))
]
Now we need an expression in place of @scheme[....] that
generates as many identifiers as there are @scheme[id] matches in
the original pattern. Since this is a common task, Scheme
provides a helper function, @scheme[generate-temporaries], that
takes a sequece of identifiers and returns a sequence of
generated identifiers:
@schemeblock[
(define-syntax (define-for-cbr stx)
(syntax-case stx ()
[(_ do-f (id ...) body)
(with-syntax ([(get ...) (generate-temporaries #'(id ...))]
[(put ...) (generate-temporaries #'(id ...))])
#'(define (do-f get ... put ...)
(define-get/put-id id get put) ...
body))]))
]
This way of generating identifiers is normally easier to think
about than tricking the macro expander into generating names with
purely pattern-based macros.
In general, the right-hand side of a @scheme[with-handlers]
binding is a pattern, just like in @scheme[syntax-case]. In fact,
a @scheme[with-handlers] form is just a @scheme[syntax-case] form
turned partially inside-out.
@; ----------------------------------------
@section[#:tag "stx-phases"]{Compile and Run-Time Phases}
As sets of macros get more complicated, you might want to write
your own helper functions, like
@scheme[generate-temporaries]. For example, to provide good
syntax-error messsage, @scheme[swap], @scheme[rotate], and
@scheme[define-cbr] all should check that certain sub-forms in
the source form are identifiers. We could use a
@scheme[check-ids] to perform this checking everywhere:
@schemeblock/eval[
#:eval check-eval
(define-syntax (swap stx)
(syntax-case stx ()
[(swap x y) (begin
(check-ids stx #'(x y))
#'(let ([tmp x])
(set! x y)
(set! y tmp)))]))
(define-syntax (rotate stx)
(syntax-case stx ()
[(rotate a c ...)
(begin
(check-ids stx #'(a c ...))
#'(shift-to (c ... a) (a c ...)))]))
]
The @scheme[check-ids] function can use the @scheme[syntax->list]
function to convert a synatx-object wrapping a list into a list
of syntax objects:
@schemeblock[
(define (check-ids stx forms)
(for-each
(lambda (form)
(unless (identifier? form)
(raise-syntax-error #f
"not an identifier"
stx
form)))
(syntax->list forms)))
]
If you define @scheme[swap] and @scheme[check-ids] in this way,
however, it doesn't work:
@interaction[
#:eval check-eval
(let ([a 1] [b 2]) (swap a b))
]
The problem is that @scheme[check-ids] is defined as a run-time
expression, but @scheme[swap] is trying to use it at compile time. In
interactive mode, compile time and run time are interleaved, but they
are not interleaved within the body of a module, and they are not
interleaved or across modules that are compiled ahead-of-time. To help
make all of these modes treat code consistently, Scheme separates the
binding spaces for different phases.
To define a @scheme[check-ids] function that can be referenced at
compile time, use @scheme[define-for-syntax]:
@schemeblock/eval[
#:eval check-eval
(define-for-syntax (check-ids stx forms)
(for-each
(lambda (form)
(unless (identifier? form)
(raise-syntax-error #f
"not an identifier"
stx
form)))
(syntax->list forms)))
]
With this for-syntax definition, then @scheme[swap] works:
@interaction[
#:eval check-eval
(let ([a 1] [b 2]) (swap a b) (list a b))
(swap a 1)
]
When organizing a program into modules, you may want to put helper
functions in one module to be used by macros that reside on other
modules. In that case, you can write the helper function using
@scheme[define]:
@schememod[#:file
"utils.ss"
scheme
(provide check-ids)
(define (check-ids stx forms)
(for-each
(lambda (form)
(unless (identifier? form)
(raise-syntax-error #f
"not an identifier"
stx
form)))
(syntax->list forms)))
]
Then, in the module that implements macros, import the helper function
using @scheme[(require (for-syntax "utils.ss"))] instead of
@scheme[(require "utils.ss")]:
@schememod[
scheme
(require (for-syntax "utils.ss"))
(define-syntax (swap stx)
(syntax-case stx ()
[(swap x y) (begin
(check-ids stx #'(x y))
#'(let ([tmp x])
(set! x y)
(set! y tmp)))]))
]
Since modules are separately compiled and cannot have circular
dependencies, the @filepath["utils.ss"] module's run-time body can be
compiled before the compiling the module that implements
@scheme[swap]. Thus, the run-time definitions in
@filepath["utils.ss"] can be used to implement @scheme[swap], as long
as they are explicitly shifted into compile time by @scheme[(require
(for-syntax ....))].
The @schememodname[scheme] module provides @scheme[syntax-case],
@scheme[generate-temporaries], @scheme[lambda], @scheme[if], and more
for use in both the run-time and compile-time phases. That is why we
can use @scheme[syntax-case] in the @scheme[mzscheme] @tech{REPL} both
directly and in the right-hand side of a @scheme[define-syntax]
form.
The @schememodname[scheme/base] module, in contrast, exports those
bindings only in the run-time phase. If you change the module above
that defines @scheme[swap] so that it uses the
@schememodname[scheme/base] language instead of
@schememodname[scheme], then it no longer works. Adding
@scheme[(require (for-syntax scheme/base))] imports
@scheme[syntax-case] and more into the compile-time phase, so that the
module works again.
Suppose that @scheme[define-syntax] is used to define a local macro in
the right-hand side of a @scheme[define-syntax] form. In that case,
the right-hand side of the inner @scheme[define-syntax] is in the
@deftech{meta-compile phase level}, also known as @deftech{phase level
2}. To import @scheme[syntax-case] into that phase level, you would
have to use @scheme[(require (for-syntax (for-syntax scheme/base)))]
or, equivalently, @scheme[(require (for-meta 2 scheme/base))].
Negative phase levels also exist. If a macro uses a helper function
that is imported @scheme[for-syntax], and if the helper function
returns syntax-object constants generated by @scheme[syntax], then
identifiers in the syntax will need bindings at @deftech{phase level
-1}, also known as the @deftech{template phase level}, to have any
binding at the run-time phase level relative to the module that
defines the macro.
@; ----------------------------------------
@include-section["certificates.scrbl"]