1. Mini-tutorial on explicit (and implicit) renaming macros in CHICKEN
  2. Implicit renaming macros
  3. How to automate matters
  4. Author
  5. Initial version
  6. Updated
  7. Updated again
  8. and again and again for chicken-5

Mini-tutorial on explicit (and implicit) renaming macros in CHICKEN

The following is an attempt to explain, how to write hygienic macros in CHICKEN with explicit renaming. It supposes, that the reader knows, how to write other low-level macros like define-macro in CHICKEN-3 and other Schemes, or defmacro in Lisp. As you will see, explicit renaming macros are even easier to write than those with define-macro. This is because there is a brute force method to avoid variable capture.

Let's consider a trivial macro a la CHICKEN-3 without any considerations on hygiene and variable capture:

(define-macro (swap! x y) ; wrong
  `(let ((tmp ,x))
      (set! ,x ,y)
      (set! ,y tmp)))

You might be surprised, but the replacement text, that within the backquotes, need not be changed in an explicit renaming macro, at least in a first step. What has to be changed, is the signature: explicit renaming transformers are always procedures of three variables, usually called form, rename, compare?, which has to be wrapped into an er-macro-transformer call:

(define-syntax swap!
  (er-macro-transformer
    (lambda (form rename compare?)
      ...
      `(let ((tmp ,x))
         (set! ,x ,y)
         (set! ,y tmp)))))

You have to destructure the form argument, which is the whole macro-from, (swap! x y) in the example, to fill in the ellipsis above. That is

(define-syntax swap! ; wrong
  (er-macro-transformer
    (lambda (form rename compare?)
      (let ((x (cadr form)) (y (caddr form)))
        `(let ((tmp ,x))
           (set! ,x ,y)
           (set! ,y tmp))))))

In this form, the new macro is the same as our first attempt with define-macro. We haven't bothered about variable capture or hygiene at all. But this macro already works. You can test it, say with

(let ((x 'x) (y 'y))
  (swap! x y)
  (list x y))

and see if it does what it is supposed to do. Of course, it is not correct, since if a client uses tmp as one of his or her arguments, the macro will crash.

In the classical macro systems, defmacro in Common Lisp or define-macro in some Schemes, you now have to think carefully, which macro variables are in danger of variable capture and use an uninterned symbol for them, tmp in the current example. Worse than that, contrary to Common Lisp, Scheme allows to use any name for variables, even keyword names as let, set!, ... so that define-macro can never create a hygienic swap! macro. Here is, where renaming comes into the play. You don't need gensym, use rename instead. And you needn't bother, which symbol to rename, rename all. This is the brute-force-method.

What does that mean, "all"? It's easy in the present example, everything within the backquote, except those symbols, which are already unquoted. But in more complicated examples, you should use expand, to see the replacement text of your macro calls.

  (pp (expand '(swap! x y)))

will result in the replacement text

(let ((tmp x))
  (set! x y)
  (set! y tmp))

Only x and y are arguments of your swap! call, hence everything else in the replacement text should be renamed: namely let, tmp and set!. So a hygienic version of swap! would be

(define-syntax swap!
  (er-macro-transformer
    (lambda (form rename compare?)
      (let ((x (cadr form)) (y (caddr form)))
        `(,(rename 'let) ((,(rename 'tmp) ,x))
           (,(rename 'set!) ,x ,y)
           (,(rename 'set!) ,y ,(rename 'tmp)))))))

If you repeat the expand call above, you will get the same replacement text, but with numbers suffixed to the renamed symbols, something like

(let11 ((tmp12 x))
  (set!13 x y)
  (set!13 y tmp12))

These renamed symbols have the same meaning as the original names without suffixes, but serve as aliases which the client cannot use under any circumstances.

Note, that the two appearances of tmp and set! are renamed to the same alias, the rename operator is referentially transparent. Note also, that these renamed symbols are much easier to interpret than gensym'ed ones!

I personally do not like these rename calls within the backquoted expression, because you loose the visual similarity between the backquoted expression and the resulting replacement text. Therefore I prefer this version, which is equivalent

(define-syntax swap!
  (er-macro-transformer
    (lambda (form rename compare?)
      (let (
        (x (cadr form))
        (y (caddr form))
        (%tmp (rename 'tmp))
        (%let (rename 'let))
        (%set! (rename 'set!))
        )
        `(,%let ((,%tmp ,x))
           (,%set! ,x ,y)
           (,%set! ,y ,%tmp))))))

You can think of the two characters ,% in the template as an identity operator.

What happens, if you forget to rename a symbol in the expansion code? Then the macro is not hygienic, which might be intended by the author. For example, anaphoric macros export a symbol on purpose, mostly named it or self. That's impossible with syntax-rules macros!

We havn't used the compare? argument in the transformer in this example. What's its role?

Well, some macros use special symbols verbatim as additional keywords, else and => in cond for example. With compare? it's possible to check, if these symbols, which might appear verbatim in a list, have the same meaning as their renamed variants (rename 'else), (rename '=>). For example, if else appears as the car of a list, lst say, you can code something like

(if (compare? (car lst) (rename 'else)) ...)

You see, explicit renaming macros are a bit more verbose than classical ones with define-macro. But everybody who is able to write the latter, is able to write the former as well. It's even easier!

Implicit renaming macros

But it can still be improved. Above, I said "You needn't bother which symbol to rename, rename all". Well, "all" is something which can perfectly be done by a machine. Consequently, since the advent of CHICKEN version 4.7, there is another low level macro system, based on ir-macro-transformer, which does this "rename all" in the background.

In explicit renaming macros, I had to tell the compiler explicitly, what to rename. Every symbol not renamed would break hygiene. In implicit renaming macros, the roles are reversed: I have to tell the compiler explicitly, which symbol should break hygiene, everything else is renamed, whence hygienic. A hygienic swap! macro now looks like this

(define-syntax swap!
  (ir-macro-transformer
    (lambda (form inject compare?)
      (let ((x (cadr form)) (y (caddr form)))
        `(let ((tmp ,x))
            (set! ,x ,y)
            (set! ,y tmp))))))

Note, that the second argument to the transformer is now called inject. It's used to "inject" a symbol unrenamed into the replacement text. Note further, that there is no need to gensym tmp as in define-macro. And last but not least, it's obligatory to wrap the transformer into an ir-macro-transformer call: the compiler must know, which low-level system to compile, after all.

For implicit renaming macros, the third argument to the transformer is now used a bit differently, for example

(if (compare? 'else (car lst)) ...)

How to automate matters

Up to now, we have always destructured the macro-code, (swap! x y), by hand. define-macro did it automatically. Is there a way to do it automatically with explicit- and implicit-renaming-macros as well?

Yes, there is. You can use the bind macro from the bindings module. It's a version of destructuring-bind of Common Lisp. Using it you can replace the let above

(let ((x (cadr form)) (y (caddr form))) ...)

by

(import-for-syntax (only bindings bind))
(bind (_ x y) form ...)

That isn't that helpful for such a simple form, but what if form is deeply nested? Then your code would be much more readable with bind, than with deeply nested lets.

But we still aren't satisfied. Ok, destructuring can be simplyfied with bind, but that can be done transparently behind the scene. We've done this in the procedural-macros egg. In particular, you'll find there two macros, define-macro and macro-rules. The latter is a procedural variant of syntax-rules, which cares for all three arguments of the transformer routine, the former a hygienic version of our good old chicken-3 macro.

The form argument of the transformer is handled automatically by bind, the rename argument can be replaced by a list of the renamed symbols, but the compare? argument must be retained. So we can write

(define-macro (swap! x y)
  (with-explicit-renaming (compare? %let %tmp %set!)
    `(,%let ((,%tmp ,x))
       (,%set! ,x ,y)
       (,%set! ,y ,%tmp))))

or -- to avoid renaming at all --

(define-macro (swap! x y)
  (with-implicit-renaming (compare?)
    `(let ((tmp ,x))
       (set! ,x ,y)
       (set! ,y tmp))))

Note, that there is neither a symbol to inject, nor one to compare. Because this happens very often, we can write in this case simply

(define-macro (swap! x y)
  `(let ((tmp ,x))
     (set! ,x ,y)
     (set! ,y tmp)))

and so we have completed a full circle, resulting in a hygienic macro now!

The following is an example for using macro-rules, a verbose if, whith additional keywords.

(import-for-syntax (only procedural-macros macro-rules))
(define-syntax vif
  (macro-rules (then else)
    ((_ test (then . xprs) (else . yprs))
     `(if ,test
        (begin ,@xprs)
        (begin ,@yprs)))))

And this is an example of an anaphoric macro, a version of lambda, which can refer to itself, and hence can be used as unnamed recursive routine.

(import-for-syntax (only procedural-macros macro-rules))
(define-syntax alambda
  (macro-rules self ()
    ((_ args xpr . xprs)
     `(letrec ((,self (lambda ,args ,xpr ,@xprs)))
        ,self))))

But note, the real power of procedural macros result from the fact, that you have complete control over the evaluation time. For example, you can use local procedures evaluated at compile time!

For details see

procedural-macros

Procedural macros are really great ...

Author

Juergen Lorenz

Initial version

Jun 09, 2009

Updated

Nov 26, 2015

Updated again

Nov 01, 2017

and again and again for chicken-5

Mar 29, 2019

Jan 02, 2020