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