CLISP 中未编译的 lambda 函数的文字源的意外修改?

Unexpected modification of literal source of uncompiled lambda functions in CLISP?

我写了下面的函数

(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) 的答案吗?

您正在修改文字数据(然后将该数据用作 lambda 函数主体的主体),这会产生未定义的后果。有关更多信息,请参阅 Unexpected persistence of data 有点令人惊讶,这是正在发生的事情,但 CLISP 似乎正在保存 lambda 中的实际源(lst 的值)函数(而不是编译它,而不是复制它)所以当你修改代码块时,你会看到引用相同代码块的 every 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)>

虽然结果看起来相同,但函数不同,因为它们捕获不同的词法环境,所以它们会做它们本来的事情应该。例如,看看如果你有 body return 变量的值会发生什么:

(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