diff --git a/index.html b/index.html index 08ab664..3e48bc8 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’s the input, Kenneth?
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

Copyright (c) 2012 by Greg Hendershott. All rights reserved. -
Last updated 2012-10-25 19:27:30

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-26 08:22:42

    1 Introduction

    2 The plan of attack

    3 Transformers

      3.1 What is a syntax transformer?

      3.2 What’s the input, Kenneth?

      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 @@ -169,16 +169,14 @@ template. Let’s try that:

foo-bar wasn’t defined.

This is where the Macro Stepper in DrRacket is invaluable. Even if you prefer to work in Emacs (like I do), this is a situation where it’s -worth using DrRacket temporarily for its Macro Stepper.

The Macro Stepper says that:

(hyphen-define/wrong1.1 foo bar () #t)

expanded to:

(define (name) #t)

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.

The thing to reach for here is with-syntax. This will let us -say that name is in effect another pattern variable, the -value of which we want to use in our main output template.

> (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 is still not defined. Back to the Macro -Stepper. It says now 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 +worth using DrRacket temporarily for its Macro Stepper.

The Macro Stepper says that the use of our macro:

(hyphen-define/wrong1.1 foo bar () #t)

expanded to:

(define (name) #t)

Well that explains it. Instead, we wanted to expand to:

(define (foo-bar) #t)

Our template is using the symbol name but we wanted its +value, such as foo-bar in this use of our macro.

A solution here is with-syntax, which lets us say that +name is something whose value can be used in our output +template:

> (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 is still not defined. Back to the Macro +Stepper. It says now we’re expanding to:

(define (|#<syntax:11:24foo>-#<syntax:11:28 bar>|) #t)

Oh right: #'a and #'b are syntax objects, and +format is printing them as such. Instead we want the datum +inside the syntax object, the symbol foo and +bar. To get that, we use syntax->datum:

> (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 diff --git a/macro-stepper.png b/macro-stepper.png new file mode 100644 index 0000000..505e130 Binary files /dev/null and b/macro-stepper.png differ diff --git a/main.rkt b/main.rkt index f1624da..d71fde8 100644 --- a/main.rkt +++ b/main.rkt @@ -600,7 +600,9 @@ This is where the Macro Stepper in DrRacket is invaluable. Even if you prefer to work in Emacs (like I do), this is a situation where it's worth using DrRacket temporarily for its Macro Stepper. -The Macro Stepper says that: +@image[#:scale 0.5 "macro-stepper.png"] + +The Macro Stepper says that the use of our macro: @codeblock{ (hyphen-define/wrong1.1 foo bar () #t) @@ -609,18 +611,21 @@ The Macro Stepper says that: expanded to: @codeblock{ - (define (name) #t) +(define (name) #t) } -We're expanding to @racket[(define (name) #t)], but we wanted to -expand to @racket[(define (foo-bar) #t)]. +Well that explains it. Instead, we wanted to expand to: -So the problem is we're getting @racket[name] when we wanted its -value, @racket[foo-bar]. +@codeblock{ +(define (foo-bar) #t) +} -The thing to reach for here is @racket[with-syntax]. This will let us -say that @racket[name] is in effect another pattern variable, the -value of which we want to use in our main output template. +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], which lets us say that +@racket[name] is something whose value can be used in our output +template: @i[ (define-syntax (hyphen-define/wrong1.3 stx) @@ -643,11 +648,10 @@ Stepper. It says now we're expanding to: (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: +Oh right: @racket[#'a] and @racket[#'b] are syntax objects, and +@racket[format] is printing them as such. Instead we want the datum +inside the syntax object, the symbol @racket[foo] and +@racket[bar]. To get that, we use @racket[syntax->datum]: @i[ (define-syntax (hyphen-define/ok1 stx)