Behaviour of special variables under macro expansion

common-lisp, macros, special-variables

Solution

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)))

Problem

``` 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?

Original source