guide: improve explanation of transformer procedures and identifier macros

This commit is contained in:
Matthew Flatt 2012-12-26 11:58:26 -06:00
parent 0a6d8822b0
commit 18a538050a
2 changed files with 81 additions and 41 deletions

View File

@ -251,7 +251,8 @@ error is reported:
@interaction[#:eval swap-eval (+ swap 3)]
An @deftech{identifier macro} works in any expression. For example, we
An @deftech{identifier macro} is a pattern-matching macro that
works in any expression. For example, we
can define @racket[clock] as an identifier macro that expands to
@racket[(get-clock)], so @racket[(+ clock 3)] would expand to
@racket[(+ (get-clock) 3)]. An identifier macro also cooperates with

View File

@ -18,22 +18,6 @@ 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 @racket[syntax-rules] and @racket[syntax-id-rules] forms are
macros that expand to procedure forms. For example, if you evaluate a
@racket[syntax-rules] form directly (instead of placing on the
right-hand of a @racket[define-syntax] form), the result is a
procedure:
@interaction[
(syntax-rules () [(nothing) something])
]
Instead of using @racket[syntax-rules], you can write your own macro
transformer procedure directly using @racket[lambda]. The argument to
the procedure is a value that represents the source form, and the
result of the procedure must be a value that represents the
replacement form.
@local-table-of-contents[]
@; ----------------------------------------
@ -136,6 +120,66 @@ with @racket[syntax-e] eventually produces the syntax objects that
were given to @racket[datum->syntax].
@; ----------------------------------------
@section[#:tag "macro-transformers"]{Macro Transformer Procedures}
Any procedure of one argument can be a @tech{macro transformer}. As
it turns out, the @racket[syntax-rules] form is a macro that expands
to a procedure form. For example, if you evaluate a
@racket[syntax-rules] form directly (instead of placing on the
right-hand of a @racket[define-syntax] form), the result is a
procedure:
@interaction[
(syntax-rules () [(nothing) something])
]
Instead of using @racket[syntax-rules], you can write your own macro
transformer procedure directly using @racket[lambda]. The argument to
the procedure is a @tech{syntax object} that represents the source form, and the
result of the procedure must be a @tech{syntax object} that represents the
replacement form:
@interaction[
#:eval check-eval
(define-syntax self-as-string
(lambda (stx)
(datum->syntax stx
(format "~s" (syntax->datum stx)))))
(self-as-string (+ 1 2))
]
The source form passed to a macro transformer represents an
expression in which its identifier is used in an application position
(i.e., after a parenthesis that starts an expression), or it
represents the identifier by itself if it is used as an expression
position and not in an application position.@margin-note*{The procedure produced by
@racket[syntax-rules] raises a syntax error if its argument
corresponds to a use of the identifier by itself, which is why
@racket[syntax-rules] does not implement an @tech{identifier macro}.}
@interaction[
#:eval check-eval
(self-as-string (+ 1 2))
self-as-string
]
The @racket[define-syntax] form supports the same shortcut
syntax for functions as @racket[define], so that the following @racket[self-as-string]
definition is equivalent to the one that uses @racket[lambda]
explicitly:
@interaction[
#:eval check-eval
(define-syntax (self-as-string stx)
(datum->syntax stx
(format "~s" (syntax->datum stx))))
(self-as-string (+ 1 2))
]
@; ----------------------------------------
@section[#:tag "syntax-case"]{Mixing Patterns and Expressions: @racket[syntax-case]}
@ -175,12 +219,11 @@ We could write the @racket[swap] macro using @racket[syntax-case]
instead of @racket[define-syntax-rule] or @racket[syntax-rules]:
@racketblock[
(define-syntax swap
(lambda (stx)
(syntax-case stx ()
[(swap x y) #'(let ([tmp x])
(set! x y)
(set! y tmp))])))
(define-syntax (swap stx)
(syntax-case stx ()
[(swap x y) #'(let ([tmp x])
(set! x y)
(set! y tmp))]))
]
One advantage of using @racket[syntax-case] is that we can provide
@ -192,21 +235,20 @@ because @racket[2] is not an identifier. We can refine our
check the sub-forms:
@racketblock[
(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)))])))
(define-syntax (swap 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, @racket[(swap x 2)] provides a syntax error
@ -241,9 +283,6 @@ set of names based on a sequence @racket[id ...]:
body) ....]))
]
@margin-note{This example uses @racket[(define-syntax (_id _arg) _body ...+)],
which is equivalent to @racket[(define-syntax _id (lambda (_arg) _body ...+))].}
In place of the @racket[....]s above, we need to bind @racket[get
...] and @racket[put ...] to lists of generated identifiers. We
cannot use @racket[let] to bind @racket[get] and @racket[put],