Scheme Macros, syntax-rules, Explicit Renaming and Links to More Insanity

This is a response to a question on Yahoo Answers about macros in Scheme. Apparently this was too long for YA, so I decided to post it here. Eventually I’ll rewrite this and make it more coherent, for now please bear with me. Note that there may be some errors here so please point them out if you find them. I am not an expert macrologist

Here’s the original question

I’m new to scheme/racket macros and here is a simplified version of the problem I’m having:

> (define-syntax let-five
(syntax-rules ()
[(let-five expr …)
(let ((five 5)) expr …)]))
> (let-five (+ five 2))

This should return 7 but instead I get an error saying that the identifier “five” is unbound.
All I want let-five to do is to bind the identifier “five” to the number 5 so that I can use “five” within let-five. The reason I want to do this is more complicated than this example so don’t tell me that this macro is doesn’t do anything useful.
I am fairly sure that I know what is wrong, but my question is what would be the correct way to write this code so that it does what I intend?
Additional Details
Jack Trades, I don’t understand what x, r, and c are and what the % and @ are. It would be nice if you could explain that.

And here’s my response…

I don’t use Racket, but Chicken gives the same error (but probably a different error message).

The problem here is that syntax-rules is a hygienic macro transformer. Which basically means that the symbol five is being renamed by syntax-rules to avoid name clashes during expansion. You can see this clearly by looking at the error message (like I said they’re probably different from yours)…

    <syntax>          (##core#let ((five119 5)) (+ five 2))

As you can see syntax-rules renames five to five119 (or something completely different). So when you try to use five it is unbound.

In Chicken I would use explicit renaming macros which preserve hygiene but allow you to break it where necessary. I’m not sure if Racket has this option (check into an implementation of syntax-case, that will allow you to do something similar).

(define-syntax (let-five x r c)
  (let ((%let (r 'let)))
    `(,%let ((five 5)) ,@(cdr x))))

(let-five (+ five 2))
;===> 7

***** EDIT *****

As I already said I don’t think Racket has explicit renaming macros so this probably won’t work for you in that case. You need to look into an implementation of syntax-case for Racket. This will provide you with the ability to do what you want. To the best of my knowledge this is not possible with syntax-rules.

That being said here’s the explanation you asked for…

x, r and c are parameter names the same as if you defined a function (lambda (x r c) …).

The x parameter is the expression, which would be (let-five (+ five 2)) in your example.

The r parameter is a renaming procedure. This is the “explicit renaming” part of the explicit renaming macro. To maintain hygiene you must use this procedure to rename every symbol that you use (except for the ones you don’t want renamed). You do this by calling (r ‘let) for example.

The c parameter is a comparison function that can compare renamed symbols for equality.

You do not provide any of those parameters. Rather the implementation of ER macros does that for you. All you need to do is provide a function that accepts 3 parameters. Another equivalent way to write the ER macro above would be this…

(define-syntax let-five
  (lambda (expression rename compare)
    (let ((%let (r 'let)))
      `(,%let ((five 5)) ,@(cdr x))))

The % sign is just an ordinary character no different from the #\l #\e or #\t. The reason I use that is to remind the reader that “%let” is the renamed symbol “let”. You could use (renamed-let (r ‘let)) if you want instead of (%let (r ‘let)).

The @ is the syntax for unquote-splicing. If you look at the last line in the ER macro above you’ll notice three forms of symbols (` , @).

` the backquote is syntax for (quasiquote (expression)). quasiquote is similar to the regular quote, except that instead of simply returning the whole expression unevaluated, it allows you to evaluate some things using the (,) syntax.

, the comma is syntax for (unquote expression) this tells Scheme to evaluate the expression that immediately follows it. So if we had the list `(1 (+ 1 1) ,(+ 1 2)) when it is evaluated it would return the list (1 (+ 1 1) 3). The expression (+ 1 1) was not evaluated because it was never unquoted, however the expression (+ 1 2) was unquoted and so it was evaluated and returned 3.

@ is syntax for splicing which is a way to evaluate an expression that returns a list and then put that list into the quasiquoted list. In other words it weaves a list into the quasiquoted list.

Here is some more information on Scheme macro systems. I read each of these articles at least a dozen times and eventually it started to come together. Even now though, I’d say that I only really understand about 10-20% of it.

Scheme macros are difficult to understand, but coming to that understanding (even if it is limited) is incredibly rewarding. It will definitely make you a better Scheme programmer and will probably help you to realize why the vast majority of other languages are simply inadequate.

This is the easiest read and the highest level overview.
A Scheme Syntax Rules Primer

This is probably the most complete approach to macros from the relatively simple to the complex. Read this until you don’t understand what it’s talking about, then play around with some of the things you learned. Then read it again from the beginning until you don’t know what it’s talking about, then play around. Then read it again… Eventually you’ll make it at least half way through and by then you’ll know more about macros than you ever thought possible.
define-syntax Primer

Here’s an interesting (and long) post on Chicken’s macro system. This covers ER macros.
Macro Systems and Chicken (long)

And finally here’s an advanced discussion of macros that may just leave you without hair.
An Advanced Syntax-Rules Primer for the Mildly Insane

Advertisements