Macros 列出未在宏内计算的gensym符号
我正在尝试编写一个宏,它接受一个变量列表和一个代码体,并确保在执行代码体后变量恢复为其原始值(Paul Graham的ANSI Common Lisp中的练习10.6) 然而,我不清楚为什么我的Macros 列出未在宏内计算的gensym符号,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,我正在尝试编写一个宏,它接受一个变量列表和一个代码体,并确保在执行代码体后变量恢复为其原始值(Paul Graham的ANSI Common Lisp中的练习10.6) 然而,我不清楚为什么我的gensym会在一个地方按我的预期进行评估,而不是在另一个类似的地方(注意:我知道有一个更好的解决方案。我只想弄清楚评估中的差异) 这里是第一个定义,其中lstgensym计算为传递给mapcar的lambda中的列表: (defmacro exec-reset-vars-1 (vars body)
gensym
会在一个地方按我的预期进行评估,而不是在另一个类似的地方(注意:我知道有一个更好的解决方案。我只想弄清楚评估中的差异)
这里是第一个定义,其中lst
gensym计算为传递给mapcar
的lambda
中的列表:
(defmacro exec-reset-vars-1 (vars body)
(let ((lst (gensym)))
`(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc))
vars
:initial-value nil)))
,@body
,@(mapcar #'(lambda (var) `(setf ,var (car ,lst)))
vars))))
但是,尽管它的工作原理与我预期的完全一致,但它并不是一个正确的解决方案,因为在尝试重置值时,我总是抓住lst
的第一个元素。我真的想映射2个列表。所以现在我写:
(defmacro exec-reset-vars-2 (vars body)
(let ((lst (gensym)))
`(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc))
vars
:initial-value nil)))
,@body
,@(mapcar #'(lambda (var val) `(setf ,var ,val))
vars
lst))))
但是现在我得到一个错误,它说
#:G3984
不是一个列表。如果我用(符号值lst)
替换它,我会得到一个错误,说变量没有值。但为什么不呢?为什么它在lambda
中的setf
中有一个值,而不是作为传递给mapcar
的参数?在宏扩展时,您尝试映射lst
的值,这是当时的一个符号。所以这毫无意义
尝试获取符号值也毫无意义,因为lst
的绑定是词法绑定,而symbol-value
不是访问该绑定的方法。其他绑定在当时不可用
显然,lst
在宏扩展时有一个值:一个符号。这是你在lambda里面看到的
您需要明确哪些值是在宏扩展时计算的,哪些值是在运行时计算的
关于命名的建议:
在Lisp中是一个糟糕的名称,请使用lst
list
- 名称
毫无意义,因为它的值不是一个列表,而是一个符号。我称之为lst
。看起来很长,不是吗?但这要清楚得多。现在,您将看到它是一个符号,用作保存列表的变量的名称列表变量符号
- 我敢肯定你想得太多了。想象一下:
(defparameter *global* 5)
(let ((local 10))
(with-reset-vars (local *global*)
(setf *global* 20)
(setf local 30)
...))
我认为扩展非常简单:
(defparameter *global* 5)
(let ((local 10))
(let ((*global* *global*) (local local))
(setf *global* 20)
(setf local 30)
...)
(print local)) ; prints 10
(print *global*) ; prints 5
让
自行重置,因此您可以看到宏应该非常简单,只需使用让
进行阴影绑定,除非我误解了赋值
你过于复杂的宏做了非常糟糕的事情。例如获取全局符号的编译时间值,这会将它们重置为函数使用此值的时间,而不是在正文之前。感谢您的回答。你完全正确。正如我所指出的,我知道这不是一个很好的解决方案,我只是想知道为什么gensym会在一个地方评估它,而不是在另一个地方。在第一个阶段中,您执行
(car lst gensym symbol)
,它提供了运行时绑定的let
-绑定的第一部分,而在第二个阶段中,您尝试在宏扩展时间映射相同的内容,但在第三阶段,它不是列表,而是一个符号值。宏本质上是代码转换,您不应该依赖变量绑定。谢谢您的回复。为了确保正确理解,在这两种情况下,lst
在宏扩展期间具有相同的值-一个符号。区别在于,在第一种情况下,展开(car,lst)
,然后在运行时评估其展开形式,此时lst
是一个列表;而在第二个例子中,作为mapcar
的参数,它在宏扩展期间被评估。这是否正确?@UnixOne:No.在运行时没有lst
,但变量的名称是宏扩展时的lst
值。