在LISP中对未编译的lambda函数的文学来源进行了意外修改?

时间:2015-01-19 21:57:00

标签: lambda common-lisp

我写了以下函数

(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)

1 个答案:

答案 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