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?

+3


source to share


1 answer


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

      

+5


source







All Articles