[plt-scheme] 'eval at expansion time

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Sun Jul 19 08:10:18 EDT 2009

At Sun, 19 Jul 2009 11:01:51 +0200, Tom Schouten wrote:
> Concretely, this is about a macro that compiles a dataflow language to
> Scheme.  The input syntax is interpreted twice: once to construct a
> simplified interpretation as a dependency graph, which is then used to
> sort a list of subforms in the original syntax so it can be expanded a
> second time into a serial Scheme program.  Concrete code for the two
> stages can be found here [1][2].  I use 'eval together with namespace
> anchors to implement this.
> 
> I guess I'm looking for a name for this pattern or a reason to not use
> 'eval.  It seems that any alternative would involve higher order
> macros in a way I don't quite grasp..

If I understand, then the following little program (in two modules) is
analogous to yours. It defines a `show' form that takes an expression
`e' to evaluate at compile time, and it prints the quoted form of `e'
followed by is compile-time result. The compile-time form can use a
`plus' binding, which stands for any sort of binding that you might
like to use during compile-time evaluation:

----------------------------------------

 ;; "arith-ct.ss"
 #lang scheme/base

 (define (plus a b)
   (+ a b))

 (define-namespace-anchor a)

 (define (show* e)
   (eval e (namespace-anchor->namespace a)))

 (provide show*)


 ;; use-arith.ss:
 #lang scheme

 (require (for-syntax "arith-ct.ss"))

 (define-syntax (show stx)
   (syntax-case stx ()
     [(_ e)
      (with-syntax ([v (show* #'e)])
        #`(printf "~s yields ~s\n" 'e 'v))]))

 (show (plus 1 2))
 ;; expands to (printf "~s yields ~s\n" '(plus 1 2) '3)

----------------------------------------

Here's how I'd implement the `show' form, instead:

----------------------------------------

 ;; "arith.ss"
 #lang scheme

 (define-for-syntax (plus a b)
   (+ a b))

 (define-syntax (show stx)
   (syntax-case stx ()
     [(_ e)
      #'(let-syntax ([exp
                      (lambda (stx)
                        (with-syntax ([v e])
                          #`(printf "~s yields ~s\n" 'e 'v)))])
          (exp))]))

 (show (plus 1 2))
 ;; expands to (printf "~s yields ~s\n" '(plus 1 2) '3)

 ;; To use `show' outside this module, we need to
 ;;  export `for-syntax' any bindings intended
 ;;  to be used within `show' expressions:
 (provide show (for-syntax plus))

----------------------------------------

This second implementation expands

 (show e)

to 

 (let-syntax ([(exp) .... e ....])
   (exp))

where `e' is in an expression position in the right-hand size of the
`let-syntax', so it gets evaluated at expansion time. The main trick is
to invent a local name (in this case `exp') to house the expand-time
expression and trigger its evaluation in an expression position.

As you suggest, this approach requires a macro-generating `show', which
is in some sense a "higher order macro".


For a problem like this, I'd avoid `eval' because the other approach
composes better. Consider a module that imports `show', but also adds
its own compile-time extension `times':

 #lang scheme
 (require "arith.ss")

 (define-for-syntax (times a b) (* a b))

 (show (plus 1 (times 2 3)))

This wouldn't work if "arith.ss" were replaced by "use-arith.ss",
because the `eval' in "arith-ct.ss" wouldn't know where to find the
module that has the `times' binding. The problem is that module is in
the process of being compiled, and so it hasn't been registered for
reflective operations like `eval'.

Your example doesn't seem to involve any bindings like `plus' or
`times', and, offhand, I can't think of another concrete reason that
"arith.ss" is better than "arith-ct.ss" plus "use-arith.ss". Less
concretely, though, the reflection in the latter seems to me more
difficult to reason about. (As it turns out, I correctly predicted that
the `times' example would not work with "use-arith.ss", but I
mispredicted the specific reason.)


I hope I've understood your actual problem well enough that the above
examples are relevant.

Matthew



Posted on the users mailing list.