commit 053c0d53dd75d41fd22c1d6dec741364c0957a7d
parent 5b517ae4b9393db592f6779c3126ad8583097e75
Author: Greg Hendershott <greghendershott@gmail.com>
Date: Thu, 1 Nov 2012 16:52:16 -0400
Get to with-syntax by way of a nested syntax-case
Rather than introduce with-syntax as a magic spell, start
by using a nested syntax-case as a more way to define
more pattern variables. Then explain show with-syntax
as simply "sugar" for this.
Diffstat:
| M | main.rkt | | | 157 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- |
1 file changed, 117 insertions(+), 40 deletions(-)
diff --git a/main.rkt b/main.rkt
@@ -723,12 +723,13 @@ No more error---good! Let's try to use it:
(foo-bar)
]
-It seems we're defining a function with a name other than
-@racket[foo-bar]?
+Apparently our macro is defining a function with some name other than
+@racket[foo-bar]. Huh.
-This is where the Macro Stepper in DrRacket is invaluable. Even if you
-prefer mostly to use Emacs, this is a situation where it's worth using
-DrRacket at least temporarily for its Macro Stepper.
+This is where the Macro Stepper in DrRacket is
+invaluable. @margin-note{Even if you prefer mostly to use Emacs, this
+is a situation where it's definitely worth temporarily using DrRacket
+for its Macro Stepper.}
@image[#:scale 0.5 "macro-stepper.png"]
@@ -753,23 +754,39 @@ Well that explains it. Instead, we wanted to expand to:
Our template is using the symbol @racket[name] but we wanted its
value, such as @racket[foo-bar] in this use of our macro.
-A solution here is @racket[with-syntax]@margin-note*{You could
-consider @racket[with-syntax] to mean, "define pattern variables".},
-which lets us say that @racket[name] is something whose value can be
-used in our output template. In effect, it lets us say that
-@racket[name] is an additional pattern variable.
+Can we think of something we already know that behaves like
+this---where using a variable in the template yields its value? Sure
+we do: Pattern variables. Our pattern doesn't include @racket[name]
+because we don't expect it in the original syntax---indeed the whole
+point of this macro is to create it. So @racket[name] can't be in the
+main pattern. Fine---let's make an @italic{additional} pattern. We can
+do that using an additional, nested @racket[syntax-case]:
@i[
-(define-syntax (hyphen-define/wrong1.3 stx)
+(define-syntax (hyphen-define/wrong1.2 stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
- (with-syntax ([name (datum->syntax stx
- (string->symbol (format "~a-~a"
- #'a
- #'b)))])
- #'(define (name args ...)
- body0 body ...))]))
-(hyphen-define/wrong1.3 foo bar () #t)
+ (syntax-case (datum->syntax stx
+ (string->symbol (format "~a-~a" #'a #'b))) ()
+ [name #'(define (name args ...)
+ body0 body ...)])]))
+]
+
+Looks weird? Let's take a deep breath. Normally our transformer
+function is given syntax by Racket, and we pass that syntax to
+@racket[syntax-case]. But we can also create some syntax of our own,
+on the fly, and pass @italic{that} to @racket[syntax-case]. That's all
+we're doing here. The whole @racket[(datum->syntax ...)] expression is
+syntax that we're creating on the fly. We can give that to
+@racket[syntax-case], and match it using a pattern variable named
+@racket[name]. Voila, we have a new pattern variable. We can use it in
+a template, and its value will go in the template.
+
+We might have one more---just one, I promise!---small problem left.
+Let's try to use our new version:
+
+@i[
+(hyphen-define/wrong1.2 foo bar () #t)
(foo-bar)
]
@@ -780,60 +797,118 @@ Stepper. It says now we're expanding to:
(define (|#<syntax:11:24foo>-#<syntax:11:28 bar>|) #t)
]
-Oh right: @racket[#'a] and @racket[#'b] are syntax objects, and
-@racket[format] is printing them as such. Instead we want the datum in
-the syntax objects (the symbols @racket[foo] and @racket[bar]). Let's
-use @racket[syntax->datum]:
+Oh right: @racket[#'a] and @racket[#'b] are syntax objects. Therefore
+
+@racketblock[(string->symbol (format "~a-~a" #'a #'b))]
+
+is something like
+
+@racketblock[|#<syntax:11:24foo>-#<syntax:11:28 bar>|]
+
+---the printed form of both syntax objects, joined by a hyphen.
+
+Instead we want the datum in the syntax objects (such as the symbols
+@racket[foo] and @racket[bar]). Let's use @racket[syntax->datum] to
+get it:
@i[
(define-syntax (hyphen-define/ok1 stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
+ (syntax-case (datum->syntax stx
+ (string->symbol (format "~a-~a"
+ (syntax->datum #'a)
+ (syntax->datum #'b)))) ()
+ [name #'(define (name args ...)
+ body0 body ...)])]))
+(hyphen-define/ok1 foo bar () #t)
+(foo-bar)
+]
+
+And now it works!
+
+Now for two shortcuts.
+
+Instead of an additional, nested @racket[syntax-case] we could use
+@racket[with-syntax]@margin-note*{Another name for
+@racket[with-syntax] could be, "define pattern variable".}. This
+rearranges the @racket[syntax-case] to look more like a @racket[let]
+statement---first the name, then the value. Also it's more convenient
+if we need to define more than one pattern variable.
+
+@i[
+(define-syntax (hyphen-define/ok2 stx)
+ (syntax-case stx ()
+ [(_ a b (args ...) body0 body ...)
(with-syntax ([name (datum->syntax stx
(string->symbol (format "~a-~a"
(syntax->datum #'a)
(syntax->datum #'b))))])
#'(define (name args ...)
body0 body ...))]))
-(hyphen-define/ok1 foo bar () #t)
+(hyphen-define/ok2 foo bar () #t)
(foo-bar)
]
-And now it works!
+Whether you use an additional @racket[syntax-case] or use
+@racket[with-syntax], either way you are simply defining an additional
+pattern variable. Don't let the terminology and structure make it seem
+mysterious.
-By the way, there is a utility function in @racket[racket/syntax]
-called @racket[format-id] that lets us format identifier names more
+Also, there is a utility function in @racket[racket/syntax] called
+@racket[format-id] that lets us format identifier names more
succinctly. As we've learned, we need to @racket[require] the module
using @racket[for-syntax], since we need it at compile time:
@i[
(require (for-syntax racket/syntax))
-(define-syntax (hyphen-define/ok2 stx)
+(define-syntax (hyphen-define/ok3 stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
(with-syntax ([name (format-id stx "~a-~a" #'a #'b)])
#'(define (name args ...)
body0 body ...))]))
-(hyphen-define/ok2 bar baz () #t)
+(hyphen-define/ok3 bar baz () #t)
(bar-baz)
]
Using @racket[format-id] is convenient as it handles the tedium of
-converting from syntax to datum and back again.
+converting from syntax to symbol datum to string ... and all the way
+back.
+Finally, here's a variation that accepts any number of name parts that
+are joined with hyphens:
+
+@i[
+(require (for-syntax racket/string racket/syntax))
+(define-syntax (hyphen-define* stx)
+ (syntax-case stx ()
+ [(_ (names ...) (args ...) body0 body ...)
+ (let* ([names/sym (map syntax-e (syntax->list #'(names ...)))]
+ [names/str (map symbol->string names/sym)]
+ [name/str (string-join names/str "-")]
+ [name/sym (string->symbol name/str)])
+ (with-syntax ([name (datum->syntax stx name/sym)])
+ #`(define (name args ...)
+ body0 body ...)))]))
+(hyphen-define* (foo bar baz) (v) (* 2 v))
+(foo-bar-baz 50)
+]
To review:
@itemize[
- @item{If you want to munge pattern variables for use in the
-template, @racket[with-syntax] is your friend.}
+ @item{You can't use a pattern variable outside of a template. But
+you can use @racket[syntax] or @tt{#'} on a pattern variable to make
+an ad hoc "fun size" template.}
- @item{You will need to use @racket[syntax] or @tt{#'} on the pattern
-variables to turn them into "fun size" templates.}
+ @item{If you want to munge pattern variables for use in the
+template, @racket[with-syntax] is your friend, because it lets you
+create new pattern variables.}
- @item{Usually you'll also need to use @racket[syntax->datum] to get
-the interesting value inside.}
+ @item{Usually you'll need to use @racket[syntax->datum] to get the
+interesting value inside.}
@item{@racket[format-id] is convenient for formatting identifier
names.}
@@ -844,10 +919,12 @@ names.}
@subsection{Making our own @racket[struct]}
-In this example we'll pretend that Racket doesn't already have a
-@racket[struct] capability. Fortunately, we can define a macro to
-provide this feature. To keep things simple, our structure will be
-immutable (read-only) and it won't support inheritance.
+Let's apply what we just learned to a more-realistic example. We'll
+pretend that Racket doesn't already have a @racket[struct]
+capability. Fortunately, we can write a macro to provide our own
+system for defining and using structures. To keep things simple, our
+structure will be immutable (read-only) and it won't support
+inheritance.
Given a structure declaration like:
@@ -855,7 +932,7 @@ Given a structure declaration like:
(our-struct name (field1 field2 ...))
]
-We need to define some procedures.
+We need to define some procedures:
@itemize[