3
FUZZ> (defvar *foo* nil)
*FOO*
FUZZ> (defmacro bar ()
        (format t "foo: ~A" *foo*)
        `(+ 1 1))
BAR
FUZZ> (defmacro bot ()
        (let ((*foo* 17))
          `(bar)))
BOT
FUZZ> (bot)
foo: NIL

My mental model (clearly wrong) of macro expansion says the following happens in order:

Run the macro expansion of bot (which binds *foo* to 17), run the macro expansion of bar, which prints the current value of *foo* (being 17), and returns the form (+ 1 1), which is not a macro, macro expansion time is now over, finally evaluate the form (+ 1 1), and returns 2.

Why am I wrong?

Is there an easy way to do what I intend?

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
gtod
  • 229
  • 2
  • 10
  • 1
    Ahhh, so my mistake was to think of the binding of `*foo*` like a dynamical variable, in effect for all the **time** it took to macroexpand `(bar)` and (crucially) all the forms it becomes in turn. Whereas in fact, once `(bar)` is returned, we are done with `*foo*`, as Joshua makes clear... – gtod Nov 21 '14 at 07:25

1 Answers1

5

When the REPL is told to evaluate (bot), it first has to perform macroexpansion. It calls the macroexpansion function bot, which means, in effect, evaluating

(let ((*foo* 17))
  `(bar))

That returns (bar) and then the binding of from let is unwound. Now we've got (bar). bar is a macro, so it's time for another round of macroexpansion, which means evaluating

(progn 
  (format t "foo: ~a" *foo*)
  `(+ 1 1))

which prints foo: NIL, and returns (+ 1 1).

If you want the macroexpansion to be performed in the scope of some bindings, you'll need to call the macroexpansion function yourself. E.g., you can use macroexpand:

CL-USER> (defparameter *foo* nil)
*FOO*
CL-USER> (defmacro bar ()
           (format t "foo: ~a" *foo*)
           `(+ 1 1))
BAR
CL-USER> (defmacro baz ()
           (let ((*foo* 42))
             (macroexpand '(bar))))
BAZ
CL-USER> (baz)
foo: 42
2

But, if you're going to do macroexpansion yourself, be sure to preserve environment arguments. In this case, a better definition of baz would be:

(defmacro baz (&environment env)
  (let ((*foo* 42))
    (macroexpand '(bar) env)))
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353