(defmacro bot () (let ((*foo* 17)) `(bar))) BOT F" /> (defmacro bot () (let ((*foo* 17)) `(bar))) BOT F" /> (defmacro bot () (let ((*foo* 17)) `(bar))) BOT F"/>

Behaviour of special variables under macro expansion

115 Views Asked by At
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?

1

There are 1 best solutions below

0
Joshua Taylor On BEST ANSWER

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