guide: improve explanation of transformer procedures and identifier macros
This commit is contained in:
parent
0a6d8822b0
commit
18a538050a
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
|
|
Loading…
Reference in New Issue
Block a user