有人可以向我解释这个非常简单的代码片段中发生了什么吗?
(defun test-a ()
(let ((x '(nil)))
(setcar x (cons 1 (car x)))
x))
第一次来电(test-a)
时,我得到了预期的结果:((1))
。
但令我惊讶的是,再次调用它,我得到((1 1))
,((1 1 1))
等等。
为什么会这样?我希望(test-a)
始终返回((1))
,这是错误的吗?
另请注意,在重新评估test-a
的定义后,返回结果将重置。
还要考虑此功能是否符合我的预期:
(defun test-b ()
(let ((x '(nil)))
(setq x (cons (cons 1 (car x))
(cdr x)))))
(test-b)
始终返回((1))
。
为什么test-a
和test-b
不等同?
答案 0 :(得分:22)
test-a
是自我修改代码。这是非常危险。当变量 x
在let
表单的末尾消失时,其初始值会一直存在于函数对象中,这就是你的值正在修改。请记住,在Lisp a function is a first class object中,可以传递(就像数字或列表一样),有时候,修改。这正是你在这里所做的:x
的初始值是函数对象的一部分,你正在修改它。
让我们实际看看发生了什么:
(symbol-function 'test-a)
=> (lambda nil (let ((x (quote (nil)))) (setcar x (cons 1 (car x))) x))
(test-a)
=> ((1))
(symbol-function 'test-a)
=> (lambda nil (let ((x (quote ((1))))) (setcar x (cons 1 (car x))) x))
(test-a)
=> ((1 1))
(symbol-function 'test-a)
=> (lambda nil (let ((x (quote ((1 1))))) (setcar x (cons 1 (car x))) x))
(test-a)
=> ((1 1 1))
(symbol-function 'test-a)
=> (lambda nil (let ((x (quote ((1 1 1))))) (setcar x (cons 1 (car x))) x))
test-b
返回一个新的cons单元格,因此是安全的。永远不会修改x
的初始值。 (setcar x ...)
和(setq x ...)
之间的区别在于前者修改已存储在变量x
中的对象,而后者存储 x
中的新对象。差异类似于x.setField(42)
中x = new MyObject(42)
与C++
的对比。
一般情况下,最好将quoted数据视为'(1)
作为常量 - 不修改它们:
quote
返回参数,而不进行评估。(quote x)
会产生x
。 警告:quote
不构造其返回值,只是返回 由Lisp阅读器预先构造的值(请参阅info节点 Printed Representation)。这意味着(a . b)
不是 与(cons 'a 'b)
相同:前者不合理。引用应该 被保留用于永远不会被副作用修改的常量, 除非你喜欢自修改代码。查看信息中常见的陷阱 节点Rearrangement表示意外结果的示例 引用的对象被修改。
如果您需要modify a list,请使用list
或cons
或copy-list
代替quote
进行创建。
PS。这已在Emacs上重复。
PPS。有关相同的Common Lisp问题,另请参阅Why does this function return a different value every time?。
答案 1 :(得分:2)
我发现罪魁祸首确实是'引用。这是它的文档字符串:
返回参数,不进行评估。
...
警告:`quote'不构造其返回值,只是返回 由Lisp读者预先构建的值
...
引用应保留给常量 除非您喜欢自我修改代码,否则永远不会被副作用修改。
为方便起见,我也重写了
(setq test-a
(lambda () ((lambda (x) (setcar x (cons 1 (car x))) x) (quote (nil)))))
然后使用
(funcall test-a)
了解'test-a是如何变化的。
答案 2 :(得分:1)
看起来你(let)中的'(nil)只被评估一次。当您(setcar)时,每个调用都在就地修改相同的列表。如果用(list(list))替换'(nil),你可以使(test-a)工作,虽然我认为有更优雅的方法。
(test-b)每次都会从cons细胞构建一个全新的列表,这就是为什么它的工作方式不同。