diff --git a/index.html b/index.html index 9d0d78a..75cab9f 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

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

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 18:15:21

    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 @@ -184,29 +184,30 @@ converting from syntax to datum and back again.

Recap: If you want to mung 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 +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 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 -variable that accidentally conflicts with one in the code that is -using our macro.

The Racket Reference -Section -1.2.3.5 Transformer Bindings. has a good explanation of this, and an -example. (You can stop when you reach the part about set! -transformers.) Basically, the input syntax has "marks" to preserve -lexical scope. This makes your macro behave like a normal function. If -a normal function defines a variable named x, it won’t -conflict with a variable named x in an outer scope.

This makes it easy to write reliable macros that behave -predictably. Unfortunately, once in awhile, we want to introduce a -magic variable like it for aif on purpose.

The way to do this is with define-syntax-parameter and +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 +syntax introduces a variable that accidentally conflicts with one in +the code surrounding our macro.

The Racket Reference section, +Transformer +Bindings, has a good explanation and example. Basically, syntax +has "marks" to preserve lexical scope. This makes your macro behave +like a normal function, for lexical scoping.

If a normal function defines a variable named x, it won’t +conflict with a variable named x in an outer scope:

> (let ([x "outer"])
    (let ([x "inner"])
      (printf "The inner `x' is ~s\n" x))
    (printf "The outer `x' is ~s\n" x))

The inner `x' is "inner"

The outer `x' is "outer"

When your macros also respect lexical scoping, it’s easy to write +reliable macros that behave predictably.

So that’s wonderful default behavior. But sometimes we want +to introduce a magic variable on purpose—such as it for +aif.

The way to do this is with a "syntax parameter", using +define-syntax-parameter and syntax-parameterize. You’re probably familiar with regular -parameters in Racket.

> (define current-foo (make-parameter "some default value"))
> (current-foo)

"some default value"

> (parameterize ([current-foo "I have a new value, for now"])
    (current-foo))

"I have a new value, for now"

> (current-foo)

"some default value"

Historically, there are other ways to do this. If you -know them, you will want to unlearn them. But if you’re the target -audience I’m writing for, you don’t know them yet. You can skip -learning them now. (Someday if you want to understand someone else’s -older macros, you can learn about them then.)

The syntax variation of them works similarly. The idea is, we’ll -define it to mean an error by default. Only inside of our -aif will it have a meaningful value:

> (require racket/stxparam)
> (define-syntax-parameter it
    (lambda (stx)
      (raise-syntax-error (syntax-e stx) "can only be used inside aif")))
> (define-syntax-rule (aif condition true-expr false-expr)
    (let ([tmp condition])
      (if tmp
          (syntax-parameterize ([it (make-rename-transformer #'tmp)])
            true-expr)
          false-expr)))
> (aif 10 (displayln it) (void))

10

> (aif #f (displayln it) (void))

If we try to use it outside of an aif form, and +parameters in Racket:

> (define current-foo (make-parameter "some default value"))
> (current-foo)

"some default value"

> (parameterize ([current-foo "I have a new value, for now"])
    (current-foo))

"I have a new value, for now"

> (current-foo)

"some default value"

Historically, there are other ways to do this. If you’re +the target audience I’m writing for, you don’t know them yet. I +suggest not bothering to learn them, yet. (Someday if you want to +understand someone else’s older macros, you can learn about them +then.)

That’s a normal parameter. The syntax variation works similarly. The +idea is that we’ll define it to mean an error by +default. Only inside of our aif will it have a meaningful +value:

> (require racket/stxparam)
> (define-syntax-parameter it
    (lambda (stx)
      (raise-syntax-error (syntax-e stx) "can only be used inside aif")))
> (define-syntax-rule (aif condition true-expr false-expr)
    (let ([tmp condition])
      (if tmp
          (syntax-parameterize ([it (make-rename-transformer #'tmp)])
            true-expr)
          false-expr)))
> (aif 10 (displayln it) (void))

10

> (aif #f (displayln it) (void))

If we try to use it outside of an aif form, and it isn’t otherwise defined, we get an error like we want:

> (displayln it)

it: can only be used inside aif

But we can still define it as a normal variable:

> (define it 10)
> it

10

6 Robust macros: syntax-parse

TO-DO. TO-DO. TO-DO.

7 Other questions

Hopefully I will answer these in the course of the other sections. But diff --git a/main.rkt b/main.rkt index 4757097..8268f3e 100644 --- a/main.rkt +++ b/main.rkt @@ -694,7 +694,7 @@ convenient for formatting identifier names. #f)) } -It would be great to write: +You could write: @codeblock{ (aif (big-long-calculation) @@ -716,29 +716,40 @@ should be easy: (aif #t (displayln it) (void)) ] -Wait, what---@racket[it] is undefined? +Wait, what? @racket[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 -variable that accidentally conflicts with one in the code that is -using our macro. +certain kind of mistake in our macros. The mistake is if our new +syntax introduces a variable that accidentally conflicts with one in +the code surrounding our macro. -The Racket Reference -@hyperlink["http://docs.racket-lang.org/reference/syntax-model.html#(part._transformer-model)" "Section -1.2.3.5 Transformer Bindings."] has a good explanation of this, and an -example. (You can stop when you reach the part about set! -transformers.) Basically, the input syntax has "marks" to preserve -lexical scope. This makes your macro behave like a normal function. If -a normal function defines a variable named @racket[x], it won't -conflict with a variable named @racket[x] in an outer scope. +The Racket @italic{Reference} section, +@hyperlink["http://docs.racket-lang.org/reference/syntax-model.html#(part._transformer-model)" "Transformer +Bindings"], has a good explanation and example. Basically, syntax +has "marks" to preserve lexical scope. This makes your macro behave +like a normal function, for lexical scoping. -This makes it easy to write reliable macros that behave -predictably. Unfortunately, once in awhile, we want to introduce a -magic variable like @racket[it] for @racket[aif] on purpose. +If a normal function defines a variable named @racket[x], it won't +conflict with a variable named @racket[x] in an outer scope: -The way to do this is with @racket[define-syntax-parameter] and +@i[ +(let ([x "outer"]) + (let ([x "inner"]) + (printf "The inner `x' is ~s\n" x)) + (printf "The outer `x' is ~s\n" x)) +] + +When your macros also respect lexical scoping, it's easy to write +reliable macros that behave predictably. + +So that's wonderful default behavior. But @italic{sometimes} we want +to introduce a magic variable on purpose---such as @racket[it] for +@racket[aif]. + +The way to do this is with a "syntax parameter", using +@racket[define-syntax-parameter] and @racket[syntax-parameterize]. You're probably familiar with regular -parameters in Racket. +parameters in Racket: @i[ (define current-foo (make-parameter "some default value")) @@ -748,15 +759,16 @@ parameters in Racket. (current-foo) ] -@margin-note{Historically, there are other ways to do this. If you -know them, you will want to unlearn them. But if you're the target -audience I'm writing for, you don't know them yet. You can skip -learning them now. (Someday if you want to understand someone else's -older macros, you can learn about them then.)} +@margin-note{Historically, there are other ways to do this. If you're +the target audience I'm writing for, you don't know them yet. I +suggest not bothering to learn them, yet. (Someday if you want to +understand someone else's older macros, you can learn about them +then.)} -The syntax variation of them works similarly. The idea is, we'll -define @racket[it] to mean an error by default. Only inside of our -@racket[aif] will it have a meaningful value: +That's a normal parameter. The syntax variation works similarly. The +idea is that we'll define @racket[it] to mean an error by +default. Only inside of our @racket[aif] will it have a meaningful +value: @i[ (require racket/stxparam)