定义一个函数,函数体中包含宏,该宏将在某个未指定的时间展开,并在此过程中使用全局动态值*test*
。
> (defvar *test* nil)
> (defmacro body ()
`(print ,*test*))
> (defun test ()
(body))
> (test)
NIL
但是,如果我想在函数定义期间将*test*
绑定到1
,那么宏展开操作时该绑定有效,并且对test
的调用产生1
而不是NIL
,该怎么办?
仅仅在let
中包装defun
不起作用:
> (let ((*test* 1))
(defun test ()
(body)))
> (test)
NIL
可能与Hyperspec中的这一行有关:
defun不需要执行任何编译时副作用
但是还有其他的方法吗?
正如您自己写的,宏在未指定的时间展开。在我的SBCL中,宏在整个表单被求值之前展开,这意味着在LET绑定生效之前。对于一些解释器,宏可能会在绑定过期后执行函数时展开。
成为Common Lisp的早期版本通过COMPILER-LET包含了这种机制,但它已经被删除了。有关更多细节,请参阅COMPILER-LET-CONFUSION问题。从词法上讲,一些效果可以使用MACROLET/SYMBOL-MACROLET来实现。从动态角度来看,很难做到这一点,如果有必要使用实际的动态绑定,我建议重新考虑这种方法。
您可以像这样引入let
:
(defvar *test* nil)
(defmacro foo ()
(let ((test (gensym)))
`(let ((,test *test*))
(print ,test))))
(defun test-foo ()
(foo))
(test-foo) => print and returns NIL
(let ((*test* 1))
(test-foo)) => print and returns 1
用宏来控制求值时间怎么样这里是将一个已知值赋给一个已知变量,但是它可以很容易地扩展来处理更多的变量,因为我们在玩使用动态变量):
(defmacro letter (&body body)
(let ((old-test *test*))
(set '*test* 1)
`(progn
,@body
(set '*test* ,old-test))))
定义test
:
(letter (defun test () (body)))
使用test
:
CL-USER> (test)
1
1
这似乎在SBCL上如预期的那样工作,需要先睡一觉在其他实现上尝试。
嗯,宏展开使得letter
可以正常工作只有在宏展开和执行时。简单的宏观扩张不会恢复*test*
到原来的值(doh)。所以这不是一个好的结合模拟器"。
我认为这是因为*test*
变量在let
的主体内是有效的。