On this page:
1.1 More than one thing
1.2 What is going on in there?
1.3 Exercises
1.4 That’s it?!
Version: 4.1.3

1 Exploring Macros

Macros can either be written using syntax-rules or syntax-case. The latter is more powerful, but the former is simpler for getting started. Hence, we’ll use syntax-rules.

Remember the grammar for CFWAE?

  <CFWAE2> ::= <num>
           || {<binop> <CFWAE2> <CFWAE2>}
           || <id>
           || {if0 <CFWAE2> <CFWAE2> <CFWAE2>}
           || {with {{<id> <CFWAE2>} ...} <CFWAE2>}
           || {fun {<id> ...} <CFWAE2>}
           || {<CFWAE2> <CFWAE2> ...}
  <binop>  ::= +,-,*,/
  <num>    ::= number?
  <id>     ::= symbol?

Lets say we want to define the syntax for if0. I’ll do that as an example.

First, we’re defining syntaxes, not functions, so we use define-syntax.

  (define-syntax if0 ....)

Next, we need to define if0 as one or more syntactic patterns. That sounds tricky, but you already know the pattern:

  {if0 <test-expression> <true-expression> <false-expression>}

You know the pattern because you know the grammar and you’ve written the parser. Therefore, writing a macro for this in Scheme is going to be a piece of cake.

To start, we say that we’re going to be writing some syntax-rules:

  (define-syntax if0
    (syntax-rules ()
      [<pattern> <generated code>]
      .... <possibly more patterns> ....))

The empty "()" means that there aren’t any keywords in our patterns. (In short, for these examples, there will always be an empty set of parens there.) The pattern is very straight-forward:

  (define-syntax if0
    (syntax-rules ()
      [{if0 test-exp true-exp false-exp}
       <generated code>]))

That might look way too easy. You should feel dirty right now... you’ve just implemented the parser for if0. Think about all that code you’ve written this semester...

Now, what should we output? We need to generate Scheme code that is equivalent to the if0 in CFWAE. That says to me that we need to do the following:

Easy-peasy?

  (define-syntax if0
    (syntax-rules ()
      [{if0 test-exp true-exp false-exp}
       (if (= test-exp 0)
           true-exp
           false-exp)]))

That’s it. However, you have to remember something important: the <generated code> is not something that runs. Instead, you have to remember that this macro is saying “if the <pattern> is matched, then you should generate, at compile-time, the <generated code>.” So, really, you can think of macros as something that extend the compiler. Or, if you prefer, they are Scheme functions that take Scheme syntax as input, and produce Scheme syntax as output. Along the way, you can re-arrange the syntax and produce all kinds of wacky new code.

1.1 More than one thing

To capture more than one piece of syntax in your pattern, you’re going to need something more, however. For example, I have said before that in CFWAE (and, for that matter, in Scheme) that with is equivalent to lambda. In Scheme, with is called let.

As it turns out, we don’t need let in our language directly: we can write it as a macro. I’ll call my let something different (like let-fu) just so things are clear.

If I write:

  (let-fu ((x 3) (y 5))
    (+ x y))

That is the same as saying

  ((lambda (x y) (+ x y)) 3 5)

We can write a macro called let-fu that will perform this transformation for us. Just as before, we start by defining a syntax transformer called let-fu:

  (define-syntax let-fu
    (syntax-rules ()
      [<pattern> <code>]))

Now, we have to decide what the pattern will be.

  (define-syntax let-fu
    (syntax-rules ()
      [(let-fu ((id exp) ...) body)
       <code>]))

The trick here is the .... The ellipsis means “zero or more.” So, we are looking for zero or more sub-expressions that have an identifier and an expression. In short, we’re looking for all the binding pairs in my let-fu expression.

Now, what do we generate? We need to generate a new lambda, and we want all the parameters of the function to be the ids, and the function to be applied to all of the exps.

  (define-syntax let-fu
    (syntax-rules ()
      [(let-fu ((id exp) ...) body)
       ((lambda (id ...) body) exp ...)]))

The rule is that anywhere we used an ellipsis in the pattern, we have to use it with those variables in any code we generate. Because we captured a list of ids, that means we must use them in the same way in our generated code.

1.2 What is going on in there?

If you want to debug your macros, and see what the macro is generating, try throwing a quote around the output of your macro. This will cause it to generate a quoted list (or, if you prefer, a list of Scheme data) so you can see the generated code instead of having it evaluated.

  (define-syntax let-fu
    (syntax-rules ()
      [(let-fu ((id exp) ...) body)
       '((lambda (id ...) body) exp ...)]))

Now, when we write a let-fu expression like:

  (let-fu ((x 3) (y 5)) (+ x y))

we get back:

  ((lambda (x y) (+ x y)) 3 5)

1.3 Exercises

Go ahead and implement macros for the following parts of CFWAE.

Like the labs, you might start with single bindings, and then do multiple bindings. The difference in this case is very, very small, but it is still work starting with the simplest case.

And that’s it. If you write these two macros, you should be able to Run them, and in the Interactions window, copy-paste some CFWAE programs and run them directly.

You will, at that point, have done the entire Extended Interpreter lab in roughly 9 lines of code and (hopefully) around 30 minutes.

1.4 That’s it?!

Macros are non-trivial to implement and get right. They are the subject of decades of research, and incredibly powerful abstractions can be built using them. Currently, it is all the rage to talk about “domain-specific languages,” or DSLs. Macros are the original tool for defining DSLs, and have been around since the early days of Lisp.

readscheme.org has a number of papers on macros. (I can’t figure out how to hyperlink to the site, so you’ll just have to copy-paste the URL.)

http://library.readscheme.org/page3.html

For more practical information about writing Scheme macros, I recommend The Scheme Programming Langauge, 3rd ed. by R. Kent Dybvig.

http://www.scheme.com/tspl3/