[plt-scheme] hygienic macros

From: Anton van Straaten (anton at appsolutions.com)
Date: Wed Apr 7 19:14:23 EDT 2004

Michael Rice wrote:

> Have improved the random-choice macro to
> (require-for-syntax (lib "1.ss" "srfi"))
> (define-syntax random-choice
>   (lambda (x)
>     (syntax-case x ()
>       ((_ expr ...)
>        (with-syntax (((index ...)
>                       (iota (length (syntax-e (syntax
> (expr ...)))))))
>          (syntax (case (random (length '(expr ...)))
>                    ((index) expr) ...)))))))

Looks good.  One point compared to the original, is that the macro will
still expand to include a length calculation on the whole original list of
expressions, which will evaluate at runtime, rather than expanding to
something of the form (random n), where n is the list length calculated at
macro expansion time.

To see an example of this, you can expand the macro as follows:

  (syntax-object->datum (expand-once (syntax
    (random-choice (* 3 4) (* 4 5) (* 6 7)))))

(The list of expressions is arbitrary.)  This gives:

  (case (random (length '((* 3 4) (* 4 5) (* 6 7))))
    ((0) (* 3 4)) ((1) (* 4 5)) ((2) (* 6 7)))

Note the length call and the quoted list embedded in it.

To get rid of that, the main thing you need to do is move the calculation of
'length' out of the syntax template.  You can do it in the with-syntax
section: add a pattern variable definition for the length of the expression
list, which I'll call 'len-stx', and use an expression similar to the
argument to iota to calculate its value.  Use that pattern variable in the
syntax template, e.g. (random len-stx).  With this change, the above
expansion should look more like:

  (case (random 3) ((0) (* 3 4)) ((1) (* 4 5)) ((2) (* 6 7)))

So now the list length is included directly in the macro's expansion, which
makes the result shorter and more efficient.

> but having difficulties eliminating all but one of the
> LENGTH computations so LENGTH is only calculated once.

With the above change, this duplication will be even more obvious.  The way
I'd improve that is to use a LET outside the with-syntax, to assign the
length to an ordinary variable, which I'll call 'len'.  You can then use
that variable in the with-syntax right hand sides.  You'll still also need a
pattern variable for the length, because you can't use an ordinary variable
directly in a syntax template (unless you're using quasisyntax).  But since
you'll now have an ordinary variable containing the length, the definition
of the pattern variable for the length can be as simple as:

  (len-stx len)

That's it!


Posted on the users mailing list.