我写了以下函数
(defun test (name)
(let ((lst (list 'lambda '()
'(let ((slot name))
nil))))
(setf (car (cdr (car (car (cdr (car (cdr (cdr lst)))))))) name)
(let ((anonymous-function (eval lst)))
(setf (fdefinition name) anonymous-function))))
如果我运行(test 'a)
,结果是(在CLISP上)#<FUNCTION :LAMBDA NIL (LET ((SLOT A)) NIL)>
;如果我运行(test 'b)
,则结果为#<FUNCTION :LAMBDA NIL (LET ((SLOT B)) NIL)>
。
而且,直到这里,没什么奇怪的。但是当我运行(fdefinition 'a)
时,我得到#<FUNCTION :LAMBDA NIL (LET ((SLOT B)) NIL)>
;如果我运行(fdefinition 'b)
,我会#<FUNCTION :LAMBDA NIL (LET ((SLOT B)) NIL)>
。难道不奇怪吗? #<FUNCTION :LAMBDA NIL (LET ((SLOT A)) NIL)>
的答案是不是(fdefinition 'a)
?
答案 0 :(得分:3)
您正在修改文字数据(然后将该数据用作lambda函数体的主体),并且具有未定义的后果。有关详情,请参阅Unexpected persistence of data。 有点令人惊讶,这就是发生的事情,但似乎CLISP正在保存lambda函数中的实际来源( lst 的值)(而不是编译它,而不是复制它)所以当你修改代码块时,你会看到引用相同代码块的每个 lambda函数中的结果发生了变化。列表中只有一个实例(let((slot name))nil),它正由您正在创建的所有不同lambda函数共享。当您修改该单个列表时,使用它的所有 lambda函数都会看到更改。
最简单的事物(即对代码的最小更改,但不一定是最佳解决方案),您可以做的是获得您想要的结果copy-tree制作一个新的代码块:
(let ((lst (copy-tree (list 'lambda '()
'(let ((slot name))
nil)))))
; ...
但是,我认为最好处理这个问题的方法就是利用Common Lisp的词法闭包并避免乱用 eval 在所有:
(defun test (name)
(let ((f (lambda ()
(let ((slot name))
nil))))
(setf (fdefinition name) f)))
CL-USER> (test 'a)
#<FUNCTION :LAMBDA NIL (LET ((SLOT NAME)) NIL)>
CL-USER> (test 'b)
#<FUNCTION :LAMBDA NIL (LET ((SLOT NAME)) NIL)>
虽然结果看起来相同,但功能却不同,因为它们捕获不同的词汇环境,因此他们按照自己的意愿行事。例如,看看如果你让身体返回变量的值会发生什么:
(defun test (name)
(let ((f (lambda ()
(let ((slot name))
slot))))
(setf (fdefinition name) f)))
CL-USER> (test 'a)
#<FUNCTION :LAMBDA NIL (LET ((SLOT NAME)) SLOT)>
CL-USER> (a)
A
CL-USER> (test 'b)
#<FUNCTION :LAMBDA NIL (LET ((SLOT NAME)) SLOT)>
CL-USER> (b)
B