diff --git a/index.html b/index.html index dd05eb1..2fff09c 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ Fear of Macros
1 Introduction
2 The plan of attack
3 Transformers
3.1 What is a syntax transformer?
3.2 What is the input?
3.3 Actually transforming the input
3.4 Compile time vs. run time
4 Pattern matching: syntax-case and syntax-rules
4.1 Patterns and templates
4.1.1 "A pattern variable can’t be used outside of a template"
5 Syntax parameters
6 Robust macros: syntax-parse
7 Other questions
7.1 What’s the point of with-syntax?
7.2 What’s the point of begin-for-syntax?
7.3 What’s the point of racket/ splicing?
8 References/ Acknowledgments
9 Epilogue

Fear of Macros

Greg Hendershott

Copyright (c) 2012 by Greg Hendershott. All rights reserved. -
Last updated 2012-10-25 13:26:46

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 +

Last updated 2012-10-25 14:43:08

    1 Introduction

    2 The plan of attack

    3 Transformers

      3.1 What is a syntax transformer?

      3.2 What is the input?

      3.3 Actually transforming the input

      3.4 Compile time vs. run time

    4 Pattern matching: syntax-case and syntax-rules

      4.1 Patterns and templates

        4.1.1 "A pattern variable can’t be used outside of a template"

    5 Syntax parameters

    6 Robust macros: syntax-parse

    7 Other questions

      7.1 What’s the point of with-syntax?

      7.2 What’s the point of begin-for-syntax?

      7.3 What’s the point of racket/splicing?

    8 References/Acknowledgments

    9 Epilogue

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—if we define a foo, such as foo-field1, foo-field2, foo?, and so on.

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—by using syntax-case again:

> (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 is undefined?

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 (|#-#|) #t)]. + +Ah, that's right. @racket[#'a] and @racket[#'b] are syntax +objects. @racket[format] is printing a representation of them as syntax +objects. What we want is the datum inside the syntax object, the +symbols @racket[foo] and @racket[bar]. So we should use +@racket[syntax->datum] on them: @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 first second () #t) -(first-second) -] - -And now it works! - -There is a shorthand for using @racket[syntax-case] this way. It's -called @racket[with-syntax]. This makes it a little simpler: - -@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 ...) + #'(define (name args ...) body0 body ...))])) -(hyphen-define/ok2 foo bar () #t) +(hyphen-define/ok1 foo bar () #t) (foo-bar) ] -Another handy thing is that @racket[with-syntax] will convert the -expression to syntax automatically. So we don't need the -@racket[datum->syntax] stuff, and now it becomes even simpler: +And now it works! + +By the way, there is a utility function in @racket[racket/syntax] +called @racket[format-id] that lets us format identifier names more +succinctly. We remember to use @racket[for-syntax] with +@racket[require], since we need it at compile time: @i[ -(define-syntax (hyphen-define/ok3 stx) +(require (for-syntax racket/syntax)) +(define-syntax (hyphen-define/ok2 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 ...) + (with-syntax ([name (format-id stx "~a-~a" #'a #'b)]) + #'(define (name args ...) body0 body ...))])) -(hyphen-define/ok3 foo bar () #t) -(foo-bar) +(hyphen-define/ok2 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. + Recap: If you want to munge pattern variables for use in the template, @racket[with-syntax] is your friend. Just remember you have to use -@racket[syntax] or @tt{#'} on the pattern variables. +@racket[syntax] or @tt{#'} on the pattern variables to turn them into +fun size templates, and often also use @racket[syntax->datum] to get +the interesting value inside. Finally, @racket[format-id] is +convenient for formatting identifier names. @; ----------------------------------------------------------------------------