宏扩展下特殊变量的行为

时间:2014-11-21 03:06:22

标签: 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

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

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

为什么我错了?

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

1 个答案:

答案 0 :(得分:5)

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

(let ((*foo* 17))
  `(bar))

返回(bar),然后解开let的绑定。现在我们已经(bar)了。 bar是一个宏,所以是时候进行另一轮宏观扩展,这意味着评估

(progn 
  (format t "foo: ~a" *foo*)
  `(+ 1 1))

打印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

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

(defmacro baz (&environment env)
  (let ((*foo* 42))
    (macroexpand '(bar) env)))