[plt-scheme] Beginner Macrology

From: Jens Axel Søgaard (jensaxel at soegaard.net)
Date: Fri Jun 15 08:56:54 EDT 2007

Keith Frost wrote:

> This works, well enough.  (It is to be understood that a bar is a
> closure with some internal state...)  But, although this is reasonably
> straightforward, I am left with an uneasy feeling that it is not the
> Scheme way.  Trolling through source code S-expressions, looking for
> an identifier in the car position to replace, that is.  Are there any
> syntax-case/or-whatever wizards reading who would care to enlighten me
> as to a better way to do this, and how it works?

Here is how I would do it:

(define-syntax (make-foo stx)
   (syntax-case stx ()
     [(make-foo maker (x) expr)
      #'(let-syntax
            ([maker
              (lambda (stx)
                (syntax-case stx ()
                  [(_ initial-state expr1)
                   (with-syntax ([bar
                                  (syntax-local-lift-expression
                                      #'(make-bar initial-state))])
                     #'(bar expr1))]))])
          (lambda (x)
            expr))]))


(define (make-bar initial-state)
   (lambda (x)
     (+ x initial-state)))

((make-foo abar (x) (+ (abar 1 x) (abar 2 (abar 3 x)))) 42)

Let us look at a simple example:

   (make-foo abar (x) (abar 1 x))

We could expand it into:

   (lambda (x)
     (abar 1 x))

*if only* abar was a macro, such that

     (abar 1 x)  expanded into (bar1 x) .

If indeed abar was such a macro then

   (lambda (x)
     (abar 1 x))

would expand into:

   (lambda (x)
     (bar1 x))

Fortunately we can introduce abar as a macro with the help
of let-syntax. That is we can expand

   (make-foo abar (x) (abar 1 x))

into

   (let-syntax ([abar <definition-of-abar>])
      (lambda (x)
         expr))  .

How do we define abar? Well, one attempt would be
to use:

   (abar 1 x)  ==> (let ([bar1 (make-bar 1)])
                      (bar1 x)

But this way the bar1 is evaluated everytime, and we
want it to be evaluated only the first time.
The solution is to lift the definition of bar1
to the toplevel.

  (abar 1 x) ==>  (with-syntax ([bar
                                  (syntax-local-lift-expression
                                      #'(make-bar initial-state))])
                     #'(bar expr1))]))])

The helper syntax-local-lift-expression binds
lifts (make-bar initial-state) to the toplevel just before
the use of make-foo. At runtime the result of
(make-bar initial-state) is bound to an identifier
(generated by syntax-local-lift-expression). We store the
identifier in bar, and can thus write #'(bar expr1)
in the body. The final result is at the top of this post.

PS: This is the fancy way of solving it. It is also
     possible to mimick your define-macro solution, but
     you wanted to see new solutions.

-- 
Jens Axel Søgaard




Posted on the users mailing list.