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.
为什么我错了?
有没有一种简单的方法可以做我想做的事情?
当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)