From 3ac5c1cc40d6548a3dd24d52dc8deadb62bf42fe Mon Sep 17 00:00:00 2001 From: Greg Hendershott Date: Sat, 27 Oct 2012 09:47:38 -0400 Subject: [PATCH] Regen html --- index.html | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index 33d5388..86fabad 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ Fear of Macros

Fear of Macros

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

1 Preface

I learned Racket after 25 years of mostly using C and C++.

Some psychic whiplash resulted.

"All the parentheses" was actually not a big deal. Instead, the first +

Last updated 2012-10-27 09:45:22

1 Preface

I learned Racket after 25 years of mostly using C and C++.

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 @@ -30,15 +30,15 @@ that soon.

But if we start there, you’re likely to feel overwhelmed and terminology, and get very confused. I did.

1. Instead let’s start with the basics: A syntax object and a function to change it (a "transformer"). We’ll work at that level for awhile to get comfortable and to de-mythologize this whole macro business.

2. Next, we’ll realize that some pattern-matching would make life -easier. We’ll learn about syntax-case, and its shorthand +easier. We’ll learn about syntax-case and its shorthand cousin, define-syntax-rule. We’ll discover we can get confused if we want to munge pattern variables before sticking them back in the template, and learn how to do that.

3. At this point we’ll be able to write many useful macros. But, what if we want to write the ever-popular anaphoric if, with a "magic variable"? It turns out we’ve been protected from making certain kind of mistakes. When we want to do this kind of thing on purpose, we use -a syntax parameter. [There are other, older ways to do -this. We won’t look at them. We also won’t spend a lot of time +a syntax parameter. [There are other, older ways to do this. We won’t +look at them. We also won’t spend a lot of time advocating "hygiene"—we’ll just stipulate that it’s good.]

4. Finally, we’ll realize that our macros could be smarter when they’re used in error. Normal Racket functions optionally can have contracts and types. These catch usage mistakes and provide clear, @@ -52,7 +52,7 @@ always outputs syntax for a string literal:

foo, please give it to my transformer function, and replace it with the syntax I give back to you." So Racket will give anything that looks like (foo ...) to our function, and we can return new syntax to use -instead. Much like a search-and-replace.

Maybe you know that the usual way to define a function in Racket:

(define (f x) ...)

is shorthand for:

(define f (lambda (x) ...))

That shorthand lets you avoid typing lambda and some parentheses.

Well there is a similar shorthand for define-syntax:

> (define-syntax (also-foo stx)
    #'"I am also foo")
> (also-foo)

"I am also foo"

What we want to remember is that this is simply shorthand. We are +instead. Much like a search-and-replace.

Maybe you know that the usual way to define a function in Racket:

(define (f x) ...)

is shorthand for:

(define f (lambda (x) ...))

That shorthand lets you avoid typing lambda and some parentheses.

Well there is a similar shorthand for define-syntax:

> (define-syntax (also-foo stx)
    #'"I am also foo")
> (also-foo)

"I am also foo"

What we want to remember is that this is simply shorthand. We are still defining a transformer function, which takes syntax and returns syntax. Everything we do with macros, will be built on top of this basic idea. It’s not magic.

Speaking of shorthand, there is also a shorthand for syntax, @@ -110,7 +110,7 @@ input syntax:

list:

> (define xs (syntax->datum stx))
> (displayln xs)

(our-if-v2 #t true false)

2. To change this into a Racket cond form, we need to take the three interesting pieces—the condition, true-expression, and false-expression—from the list using cadr, caddr, -and cadddr and arrange them into a cond form:

`(cond [,(cadr xs) ,(caddr xs)]
       [else ,(cadddr xs)])

3. Finally, we change that into syntax using +and cadddr and arrange them into a cond form:

`(cond [,(cadr xs) ,(caddr xs)]
       [else ,(cadddr xs)])

3. Finally, we change that into syntax using datum->syntax:

> (datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
                            [else ,(cadddr xs)]))

#<syntax (cond (#t "true") (else "fals...>

So that works, but using cadddr etc. to destructure a list is painful and error-prone. Maybe you know Racket’s match? Using that would let us do pattern-matching.

Notice that we don’t care about the first item in the @@ -134,7 +134,7 @@ macro? One way to do that is put it in another module, and the racket/match module.

If instead we want to put the helper in the same module, we can’t simply define it and use it—the definition would exist at run time, but we need it at compile time. The answer is to put the -definition of the helper function(s) inside begin-for-syntax:

(begin-for-syntax
 (define (my-helper-function ....)
   ....)
 (define-syntax (macro-using-my-helper-function stx)
   (my-helper-function ....)
   ....))

To review:

  • Syntax transformers work at compile time, not run time. The good +definition of the helper function(s) inside begin-for-syntax:

    (begin-for-syntax
     (define (my-helper-function ....)
       ....)
     (define-syntax (macro-using-my-helper-function stx)
       (my-helper-function ....)
       ....))

    To review:

    • Syntax transformers work at compile time, not run time. The good news is this means we can do things like rearrange the pieces of syntax without evaluating them. We can implement forms like if that simply couldn’t work properly as run time functions.

    • More good news is that there isn’t some special, weird language @@ -155,7 +155,7 @@ possible but tedious using list accessors such as added to Racket later.

It turns out that pattern-matching was one of the first improvements to be added to the Racket macro system. It’s called syntax-case, and has a shorthand for simple situations called -define-syntax-rule.

Recall our previous example:

(require (for-syntax racket/match))
(define-syntax (our-if-using-match-v2 stx)
  (match (syntax->list stx)
    [(list _ condition true-expr false-expr)
     (datum->syntax stx `(cond [,condition ,true-expr]
                               [else ,false-expr]))]))

Here’s what it looks like using syntax-case:

> (define-syntax (our-if-using-syntax-case stx)
    (syntax-case stx ()
      [(_ condition true-expr false-expr)
       #'(cond [condition true-expr]
               [else false-expr])]))
> (our-if-using-syntax-case #t "true" "false")

"true"

Pretty similar, huh? The pattern matching part looks almost exactly +define-syntax-rule.

Recall our previous example:

(require (for-syntax racket/match))
(define-syntax (our-if-using-match-v2 stx)
  (match (syntax->list stx)
    [(list _ condition true-expr false-expr)
     (datum->syntax stx `(cond [,condition ,true-expr]
                               [else ,false-expr]))]))

Here’s what it looks like using syntax-case:

> (define-syntax (our-if-using-syntax-case stx)
    (syntax-case stx ()
      [(_ condition true-expr false-expr)
       #'(cond [condition true-expr]
               [else false-expr])]))
> (our-if-using-syntax-case #t "true" "false")

"true"

Pretty similar, huh? The pattern matching part looks almost exactly the same. The way we specify the new syntax is simpler. We don’t need to do quasi-quoting and unquoting. We don’t need to use datum->syntax. Instead, we supply a "template", which uses @@ -192,11 +192,11 @@ size" 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 mostly 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 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 +it’s 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 +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 @@ -207,7 +207,7 @@ converting from syntax to datum and back again.

To review:

  • I template, with-syntax is your friend.

  • You will need to use syntax or # on the pattern variables to turn them into "fun size" templates.

  • Usually you’ll also need to use syntax->datum to get the interesting value inside.

  • 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))

You could write:

(aif (big-long-calculation)
     (foo it)
     #f)

In other words, when the condition is true, an it 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))

You could 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 if our new @@ -244,16 +244,18 @@ Looking Hygiene, which explains syntax-parameterize. I relied heavily on that, mostly just updating it since his post was written before PLT Scheme was renamed to Racket.

After initially wondering if I was asking the wrong question and conflating two different issues :), Shriram Krishnamurthi looked at an -early draft and encouraged me to keep going. Sam Tobin-Hochstadt also -encouraged me.

After writing much of this, I noticed that Racket’s documentation had -improved since I last read it. Actually it was the same, and very -good—I’d changed. It’s interesting how much of what we already know -is projected between the lines. That’s what makes it so hard to write -documentation. The only advantage I had was knowing so much less.

9 Epilogue

"Before I had studied Chan (Zen) for thirty years, I saw mountains as +early draft and encouraged me to keep going. Sam Tobin-Hochstadt and +Robby Findler also encouraged me.

Finally, I noticed something strange. After writing much of this, when +I returned to some parts of the Racket documentation, I noticed it had +improved since I last read it. Of course, it was the same. I’d +changed. It’s interesting how much of what we already know is +projected between the lines. My point is, the Racket documentation is +very good. The Guide provides helpful examples and +tutorials. The Reference is very clear and precise.

9 Epilogue

"Before I had studied Chan (Zen) for thirty years, I saw mountains as mountains, and rivers as rivers. When I arrived at a more intimate knowledge, I came to the point where I saw that mountains are not mountains, and rivers are not rivers. But now that I have got its very substance I am at rest. For it’s just that I see mountains once again as mountains, and rivers once again as rivers"

–Buddhist saying originally formulated by Qingyuan Weixin, later translated by D.T. Suzuki in his Essays in Zen -Buddhism.

Translated into Racket:

(dynamic-wind (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers)))
              (lambda ()
                (not (and (eq? 'mountains 'mountains)
                          (eq? 'rivers 'rivers))))
              (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers))))
 
\ No newline at end of file +Buddhism.

Translated into Racket:

(dynamic-wind (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers)))
              (lambda ()
                (not (and (eq? 'mountains 'mountains)
                          (eq? 'rivers 'rivers))))
              (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers))))
 
\ No newline at end of file