Macro expansion behavior of special variables
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 of the (clearly wrong) macro expansion says the following happens in order:
Run a macro expansion bot
(which links *foo*
to 17
), run a macro expansion bar
that prints the current value *foo*
(being 17
) and returns a form (+ 1 1)
that is not a macro, the macro expansion time is already over, finally evaluate the form (+ 1 1)
and return 2
.
Why am I wrong?
Is there an easy way to do what I intend?
source to share
When the REPL is prompted to evaluate (bot)
, the macro expansion needs to be done first. It calls the macro expansion function bot
, which means essentially evaluating
(let ((*foo* 17))
`(bar))
This returns (bar)
and then the binding from is let
unwound. Now we have (bar)
. bar
- macro, so time for another round of macro expansion, which means evaluation
(progn
(format t "foo: ~a" *foo*)
`(+ 1 1))
which prints foo: NIL
and returns (+ 1 1)
.
If you want macro definition to be executed within some bindings, you need to call the macro expansion function yourself. For example, 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 are going to do macro exposure yourself, be sure to save the environment arguments . In this case, the best definition baz
would be:
(defmacro baz (&environment env)
(let ((*foo* 42))
(macroexpand '(bar) env)))
source to share