macros chapter in guide
svn: r9927
This commit is contained in:
parent
ba895e827f
commit
6a93d0c531
|
@ -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)
|
||||
|
|
|
@ -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 ...)))]
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 ()
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
481
collects/scribblings/guide/pattern-macros.scrbl
Normal file
481
collects/scribblings/guide/pattern-macros.scrbl
Normal 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].
|
||||
|
462
collects/scribblings/guide/proc-macros.scrbl
Normal file
462
collects/scribblings/guide/proc-macros.scrbl
Normal 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"]
|
Loading…
Reference in New Issue
Block a user