16

有人可以向我解释这个非常简单的代码片段中发生了什么吗?

(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-atest-b等价的?

4

3 回答 3

22

坏的

test-a自修改代码。这是极其危险的。虽然变量 在表单x末尾消失,但它的初始值仍然存在于函数对象中,这就是您正在修改的值。请记住,在 Lisp 中,函数是第一类对象,可以传递(就像数字或列表一样),有时也可以修改。这正是您在这里所做的:初始值是函数对象的一部分,您正在修改它。letx

让我们实际看看发生了什么:

(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)vs. x = new MyObject(42)in C++

底线

通常,最好将引用的数据'(1)视为常量 - 不要修改它们:

quote返回参数,而不评估它。 (quote x)产量x警告quote不构造它的返回值,而只是返回由 Lisp 阅读器预先构造的值(参见信息节点 Printed Representation)。这意味着(a . b)不等同于(cons 'a 'b):前者没有缺点。引号应该保留给永远不会被副作用修改的常量,除非您喜欢自修改代码。有关修改引用对象时意外结果的示例,请参阅信息节点重新排列中的常见陷阱。

如果您需要修改列表list,请使用orconscopy-list代替 来创建它quote

查看更多 示例

PS。这已在Emacs上复制。

聚苯乙烯。另请参阅为什么此函数每次都返回不同的值?对于相同的 Common Lisp 问题。

于 2013-05-21T15:08:02.120 回答
2

我发现罪魁祸首确实是“报价”。这是它的文档字符串:

返回参数,而不对其进行评估。

...

警告:`quote' 不构造它的返回值,而只是返回由 Lisp 阅读器预先构造的值

...

引号应该保留给永远不会被副作用修改的常量,除非您喜欢自修改代码。

为了方便我也重写了

(setq test-a 
      (lambda () ((lambda (x) (setcar x (cons 1 (car x))) x) (quote (nil)))))

然后用

(funcall test-a)

看看 'test-a 是如何变化的。

于 2013-05-22T12:12:42.990 回答
1

看起来你的 (let) 中的 '(nil) 只被评估一次。当您(setcar)时,每个调用都在原地修改相同的列表。如果将 '(nil) 替换为 (list (list)),则可以使 (test-a) 工作,尽管我认为有一种更优雅的方法可以做到这一点。

(test-b) 每次都从 cons 单元构造一个全新的列表,这就是它工作方式不同的原因。

于 2013-05-21T13:28:06.893 回答