我正在尝试编写一个宏,它接受一个变量列表和一组代码,并确保在执行代码体之后变量恢复为原始值(在Paul Graham的 ANSI Common Lisp中练习10.6 < / em>的)。
但是,我不清楚为什么我的gensym
会在一个地方进行评估,而不是另一个类似的评估(注意:我知道有一个更好的解决方案我想弄清楚为什么评价会有差异。
以下是lst
gensym评估为传递给lambda
的{{1}}内部列表的第一个定义:
mapcar
但是虽然它完全按照我的预期工作,但它并不是练习的正确解决方案,因为在尝试重置值时,我总是抓住(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))))
的第一个元素。我真的想映射2个列表。所以现在我写道:
lst
但现在我收到一条错误消息,指出(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
替换它,我得到一个错误,说变量没有值。但为什么不呢?为什么它在(symbol-value lst)
中的setf
内有值,而不是作为传递给lambda
的参数?
答案 0 :(得分:6)
在宏展开时,您尝试映射lst
的值,这是当时的符号。所以这毫无意义。
尝试获取符号值也没有意义,因为lst
的绑定是词法,symbol-value
不是访问它的方法。当时没有其他绑定。
显然lst
在宏展开时有一个值:一个符号。这就是你在lambda中看到的内容。
您需要明确在宏展开时和运行时计算的值。
关于命名的建议:
lst
在Lisp中名称不佳,使用list
lst
毫无意义,因为它的值不是列表,而是符号。我称之为list-variable-symbol
。看起来很长,不是吗?但它更清楚。你现在想它是一个符号,用作变量持有列表的名称。答案 1 :(得分:3)
我很确定你是在过度思考它。想象一下:
(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
let
重置它自己,所以你看到宏应该非常简单,只需要与let
进行阴影绑定,除非我误解了赋值。
你过于复杂的宏做得非常糟糕。就像获取全局符号编译时的值一样,这会将它们重置为使用它而不是在主体之前的函数。