Fear of Macros

1 Introduction
I learned Racket after 25 years of doing C/C++ imperative programming.
Some psychic whiplash resulted.
"All the parentheses" was actually not a big deal. Instead, the first +
1 Introduction
I learned Racket after 25 years of doing C/C++ imperative programming.
Some psychic whiplash resulted.
"All the parentheses" was actually not a big deal. Instead, the first mind warp was functional programming. Before long I wrapped my brain around it, and went on to become comfortable and effective with many other aspects and features of Racket.
But two final frontiers remained: Macros and continuations.
I found that simple macros were easy and understandable, plus there
@@ -156,19 +156,33 @@ form does something like this—
So let’s pretend we’re doing something like that. We want to transform the syntax (hyphen-define a b (args) body) to -the syntax (define (a-b args) body).
A wrong first attempt is:
> (define-syntax (hyphen-define/wrong stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (let ([name (string->symbol (format "~a-~a" a b))]) #'(define (name args ...) body0 body ...))])) eval:46:0: a: pattern variable cannot be used outside of a
template
in: a
Huh. We have no idea what this error message means. Well, let’s see. +the syntax (define (a-b args) body).
A wrong first attempt is:
> (define-syntax (hyphen-define/wrong1 stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (let ([name (string->symbol (format "~a-~a" a b))]) #'(define (name args ...) body0 body ...))])) eval:46:0: a: pattern variable cannot be used outside of a
template
in: a
Huh. We have no idea what this error message means. Well, let’s see. The "template" is the #'(define (name args ...) body0 body ...) portion. The let isn’t part of that template. It sounds like we can’t use a (or b) in the -let part.
It turns out we can use a pattern variable in another
-pattern—
> (define-syntax (hyphen-define/wrong 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 ...)])])) eval:47:0: a: pattern variable cannot be used outside of a
template
in: a
I don’t have a clear explanation for why they -need to be #’a and #’b. Can anyone help?
Well, not quite. We can’t use a and b directly. We -have to wrap each one in syntax, or use its reader alias, -#’:
> (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 first second () #t) > (first-second) #t
And now it works!
There is a shorthand for using syntax-case this way. It’s -called with-syntax. This makes it a little simpler:
> (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/ok2 foo bar () #t) > (foo-bar) #t
Another handy thing is that with-syntax will convert the -expression to syntax automatically. So we don’t need the -datum->syntax stuff, and now it becomes even simpler:
> (define-syntax (hyphen-define/ok3 stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (with-syntax ([name (string->symbol (format "~a-~a" (syntax->datum #'a) (syntax->datum #'b)))]) #`(define (name args ...) body0 body ...))])) > (hyphen-define/ok3 foo bar () #t) > (foo-bar) #t
Recap: If you want to munge pattern variables for use in the template, +let part.
It turns out that syntax-case can have multiple +templates. The final expression is the obvious template, used to +create the output syntax. But you can use syntax a.k.a. #’ on +a pattern variable, to make a template. Let’s try that:
> (define-syntax (hyphen-define/wrong1.1 stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (let ([name (string->symbol (format "~a-~a" #'a #'b))]) #'(define (name args ...) body0 body ...))])) > (hyphen-define/wrong1.1 foo bar () #t) > (foo-bar) foo-bar: undefined;
cannot reference an identifier before its definition
in module: 'program
Our macro definition didn’t give an error, but when we tried to use +it, it didn’t work. It seems that foo-bar didn’t get defined.
This is where the Macro Stepper in DrRacket is invaluable. Even if you +prefer to work in Emacs (like I do), in a situation like this it’s +worth firing up DrRacket temporarily to use the Macro Stepper.
It shows us:
(module anonymous-module racket (#%module-begin (define-syntax (hyphen-define/wrong1.1 stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (let ([name (string->symbol (format "~a-~a" #'a #'b))]) #'(define (name args ...) body0 body ...))])) (define (name) #t)))
It shows that we’re expanding to (define (name) #t), but we +wanted to expand to (define (foo-bar) #t).
So the problem is we’re getting name when we wanted its +value, foo-bar.
> (define-syntax (hyphen-define/wrong1.2 stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (let ([name (string->symbol (format "~a-~a" #'a #'b))]) #'(define (name args ...) body0 body ...))])) > (hyphen-define/wrong1.2 foo bar () #t) > (foo-bar) foo-bar: undefined;
cannot reference an identifier before its definition
in module: 'program
The thing to reach for here is with-syntax. This will say +that name is in effect another pattern variable, and to use +its value:
> (define-syntax (hyphen-define/wrong1.3 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) > (foo-bar) foo-bar: undefined;
cannot reference an identifier before its definition
in module: 'program
Hmm. foo-bar still not defined. Back to the Macro Stepper. It says we’re expanding to:
(define (|#<syntax:11:24foo>-#<syntax:11:28 bar>|) #t).
Ah, that’s right. #'a and #'b are syntax +objects. format is printing a representation of them as syntax +objects. What we want is the datum inside the syntax object, the +symbols foo and bar. So we should use +syntax->datum on them:
> (define-syntax (hyphen-define/ok1 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) > (foo-bar) #t
And now it works!
By the way, there is a utility function in racket/syntax +called format-id that lets us format identifier names more +succinctly. We remember to use for-syntax with +require, since we need it at compile time:
> (require (for-syntax racket/syntax))
> (define-syntax (hyphen-define/ok2 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) > (bar-baz) #t
Using format-id is convenient as it handles the tedium of +converting from syntax to datum and back again.
Recap: If you want to munge pattern variables for use in the template, with-syntax is your friend. Just remember you have to use -syntax or #’ on the pattern variables.
5 Syntax parameters
"Anaphoric if" or "aif" is a popular macro example. Instead of writing:
(let ([tmp (big-long-calculation)]) (if tmp (foo tmp) #f))
It would be great to write:
(aif (big-long-calculation) (foo it) #f)
In other words, when the condition is true, an it identifier +syntax or #’ on the pattern variables to turn them into +fun size templates, and often also use syntax->datum to get +the interesting value inside. Finally, format-id is +convenient for formatting identifier names.
5 Syntax parameters
"Anaphoric if" or "aif" is a popular macro example. Instead of writing:
(let ([tmp (big-long-calculation)]) (if tmp (foo tmp) #f))
It would be great to write:
(aif (big-long-calculation) (foo it) #f)
In other words, when the condition is true, an it identifier is automatically created and set to the value of the condition. This should be easy:
> (define-syntax-rule (aif condition true-expr false-expr) (let ([it condition]) (if it true-expr false-expr))) > (aif #t (displayln it) (void)) it: undefined;
cannot reference an identifier before its definition
in module: 'program
Wait, what—
It turns out that all along we have been protected from making a
certain kind of mistake in our macros. The mistake is to introduce a
diff --git a/main.rkt b/main.rkt
index 645fae6..88c8659 100644
--- a/main.rkt
+++ b/main.rkt
@@ -550,7 +550,7 @@ the syntax @racket[(define (a-b args) body)].
A wrong first attempt is:
@i[
-(define-syntax (hyphen-define/wrong stx)
+(define-syntax (hyphen-define/wrong1 stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
(let ([name (string->symbol (format "~a-~a" a b))])
@@ -564,80 +564,118 @@ The "template" is the @racket[#'(define (name args ...) body0 body
sounds like we can't use @racket[a] (or @racket[b]) in the
@racket[let] part.
-It turns out we can use a pattern variable in @italic{another}
-pattern---by using @racket[syntax-case] @italic{again}:
+It turns out that @racket[syntax-case] can have multiple
+templates. The final expression is the obvious template, used to
+create the output syntax. But you can use @racket[syntax] a.k.a. #' on
+a pattern variable, to make a template. Let's try that:
@i[
-(define-syntax (hyphen-define/wrong stx)
+(define-syntax (hyphen-define/wrong1.1 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 ...)])]))
+ (let ([name (string->symbol (format "~a-~a" #'a #'b))])
+ #'(define (name args ...)
+ body0 body ...))]))
+(hyphen-define/wrong1.1 foo bar () #t)
+(foo-bar)
]
-@margin-note{I don't have a clear explanation for @italic{why} they
-need to be @tt{#'a} and @tt{#'b}. Can anyone help?}
+Our macro definition didn't give an error, but when we tried to use
+it, it didn't work. It seems that foo-bar didn't get defined.
-Well, not quite. We can't use @racket[a] and @racket[b] directly. We
-have to wrap each one in @racket[syntax], or use its reader alias,
-@tt{#'}:
+This is where the Macro Stepper in DrRacket is invaluable. Even if you
+prefer to work in Emacs (like I do), in a situation like this it's
+worth firing up DrRacket temporarily to use the Macro Stepper.
+
+It shows us:
+
+@codeblock{
+(module anonymous-module racket
+ (#%module-begin
+ (define-syntax (hyphen-define/wrong1.1 stx)
+ (syntax-case stx ()
+ [(_ a b (args ...) body0 body ...)
+ (let ([name (string->symbol (format "~a-~a" #'a #'b))])
+ #'(define (name args ...) body0 body ...))]))
+ (define (name) #t)))
+}
+
+It shows that we're expanding to @racket[(define (name) #t)], but we
+wanted to expand to @racket[(define (foo-bar) #t)].
+
+So the problem is we're getting @racket[name] when we wanted its
+value, @racket[foo-bar].
+
+The thing to reach for here is @racket[with-syntax]. This will say
+that @racket[name] is in effect another pattern variable, the value of
+which we want to use in our main output template.
+
+@i[
+(define-syntax (hyphen-define/wrong1.3 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)
+(foo-bar)
+]
+
+Hmm. @racket[foo-bar] still not defined. Back to the Macro Stepper. It says we're expanding to:
+
+@racket[(define (|#