宏扩展下特殊变量的行为

gto*_*tod 3 macros common-lisp 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
Run Code Online (Sandbox Code Playgroud)

宏观扩张的心理模型(显然是错误的)表示以下顺序发生:

运行宏扩展bot(绑定*foo*17),运行宏扩展bar,打印当前值*foo*(正在17),并返回表单(+ 1 1),这不是宏,宏扩展时间现在结束,最后评估表单(+ 1 1),并返回2.

为什么我错了?

有没有一种简单的方法可以做我想做的事情?

Jos*_*lor 5

当REPL被告知要评估时(bot),它首先必须执行宏扩展.它调用宏扩展函数bot,这实际上意味着评估

(let ((*foo* 17))
  `(bar))
Run Code Online (Sandbox Code Playgroud)

返回(bar)然后从中let解除绑定.现在我们有了(bar). bar是一个宏观,所以是时候进行另一轮宏观扩张,这意味着评估

(progn 
  (format t "foo: ~a" *foo*)
  `(+ 1 1))
Run Code Online (Sandbox Code Playgroud)

打印foo: NIL和返回(+ 1 1).

如果希望在某些绑定的范围内执行宏展开,则需要自己调用宏展开函数.例如,您可以使用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
Run Code Online (Sandbox Code Playgroud)

但是,如果您要自己进行宏扩展,请务必保留环境参数.在这种情况下,更好的定义baz是:

(defmacro baz (&environment env)
  (let ((*foo* 42))
    (macroexpand '(bar) env)))
Run Code Online (Sandbox Code Playgroud)