[plt-scheme] hygienic macros

From: Anton van Straaten (anton at appsolutions.com)
Date: Sat Apr 3 18:17:17 EST 2004

> To firm up my understanding of hygienic macros I have
> been trying to translate a few from the old backquote
> syntax to the new define-syntax, case-syntax, etc.
>
> The latest one I chose is a random-choice macro from
> Paul Graham's ANSI Common Lisp (pg. 170):
>
> (defmacro random-choice (&rest exprs)
> `(case (random ,(length exprs))
>    , at let ((key -1))
>      (mapcar #'(lambda (expr)
>                  `(,(incf key) ,expr))
>              exprs))))
>
>
> My translation(shorter pick replaces random-choice):
>
> ;randomly pick an expression to evaluate
> (define-syntax pick
>   (lambda (x)
>     (syntax-case x ()
>       ((_ exprs ...)
>        (with-syntax (((index ...)
>                       (let f ((i 0) (ls (syntax-e (syntax (exprs ...)))))
>                         (if (null? ls)
>                             '()
>                             (cons i (f (+ i 1) (cdr ls)))))))
>          (syntax (pick-help (index ...) (exprs ...))))))))

Writing inline code to do something as basic as generate a list of numbers
tends to obscure the code.  It's better to either extract that code to a
separate function, or better yet, to use a standard library function like
'iota' from SRFI 1.  Here's the same code using iota:

(require-for-syntax (lib "1.ss" "srfi"))

(define-syntax pick
  (lambda (x)
    (syntax-case x ()
      ((_ exprs ...)
       (with-syntax (((index ...) (iota (length (syntax-e (syntax (exprs
...)))))))
         (syntax (pick-help (index ...) (exprs ...))))))))

On to pick-help:

> (define-syntax pick-help
>   (lambda (x)
>     (syntax-case x ()
>       ((_ (index ...) (exprs ...))
>        (with-syntax (((clauses ...)
>                       (map (lambda (i e) (list (list i) e))
>                            (syntax-e (syntax (index ...)))
>                            (syntax-e (syntax (exprs ...))))))
>          (syntax (case (random (length '(clauses ...))) clauses ...)))))))

It's possible to make better use of the template expansion capabilities of
syntax-case here.  A clue is the use of 'map' to generate syntax.  In many
cases, you can eliminate 'map' and replace it with the appropriate syntax
template, using the "..." feature to repeat what needs to be repeated.

In this case, the syntax you want is as simple as this:

(syntax
 (case (random (length '(expr ...)))
   ((index) expr)
   ...))

You don't need to use map with this, all you need is this:

(define-syntax pick-help
  (lambda (x)
    (syntax-case x ()
      ((_ (index ...) (expr ...))
       (syntax
        (case (random (length '(expr ...)))
          ((index) expr)
          ...))))))

This is missing one thing compared to the defmacro version, which is that
it's determining the length of the expression list at runtime, instead of at
macro-expansion time.  To correct that, you can use with-syntax to bind a
pattern variable to the calculated length of the expression list, and use
that pattern variable in the syntax template.  I'll leave that as an
exercise.

Finally, pick-help is now small enough that it could make sense to combine
it with the main macro, unless you think you'll be reusing it in some other
context.  After combining the macros, you'll be determining the length of
the expression list twice in the same macro - once for iota, and once for
the argument to random.  To perfect the macro, determine the length only
once.

Anton



Posted on the users mailing list.