问题描述
谁能向我解释一下这个非常简单的代码片段发生了什么?
Could someone explain to me what's going on in this very simple code snippet?
(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
的定义后,返回结果重置.
Upon a calling (test-a)
for the first time, I get the expected result: ((1))
.But to my surprise, calling it once more, I get ((1 1))
, ((1 1 1))
and so on.Why is this happening? Am I wrong to expect (test-a)
to always return ((1))
?Also note that after re-evaluating the definition of test-a
, the return result resets.
还要考虑到这个函数按我的预期工作:
Also consider that this function works as I expect:
(defun test-b ()
(let ((x '(nil)))
(setq x (cons (cons 1 (car x))
(cdr x)))))
(test-b)
总是返回 ((1))
.为什么 test-a
和 test-b
不是等价的?
(test-b)
always returns ((1))
.Why aren't test-a
and test-b
equivalent?
推荐答案
The Bad
test-a
是自修改代码.这极其危险.虽然变量 x
在let
形式的末尾消失了,但它的初始值 仍然存在于函数对象中,这就是您正在修改的值.请记住,在 Lisp 一个函数是第一类对象,它可以被传递(就像数字或列表一样),有时还修改.这正是您在这里所做的: x
的初始值是函数对象的一部分,您正在修改它.
The Bad
test-a
is self-modifying code. This is extremely dangerous. While the variable x
disappears at the end of the let
form, its initial value persists in the function object, and that is the value you are modifying. Remember that in Lisp a function is a first class object, which can be passed around (just like a number or a list), and, sometimes, modified. This is exactly what you are doing here: the initial value for x
is a part of the function object and you are modifying it.
让我们实际看看发生了什么:
Let us actually see what is happening:
(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
中存储一个 new 对象.区别类似于C++
中的x.setField(42)
vs. x = new MyObject(42)
.
The Good
test-b
returns a fresh cons cell and thus is safe. The initial value of x
is never modified. The difference between (setcar x ...)
and (setq x ...)
is that the former modifies the object already stored in the variable x
while the latter stores a new object in x
. The difference is similar to x.setField(42)
vs. x = new MyObject(42)
in C++
.
一般来说,最好对待引用 像 '(1)
这样的数据作为常量 - 不要不要修改它们:
In general, it is best to treat quoted data like '(1)
as constants - do not modify them:
quote
返回参数,而不计算它.(quote x)
产生 x
.警告:quote
不构造它的返回值,而只是返回由 Lisp 阅读器预先构造的值(请参阅信息节点印刷表示).这意味着 (a . b)
不是与 (cons 'a 'b)
相同:前者没有缺点.引用应该保留给永远不会被副作用修改的常量,除非你喜欢自修改代码.查看信息中的常见陷阱节点 重排 意外结果示例什么时候引用的对象被修改.
如果您需要修改列表,使用 list
或 cons
或 copy-list
而不是 quote
创建它.
If you need to modify a list, create it with list
or cons
or copy-list
instead of quote
.
附注.这已在 Emacs 上重复.
PS. This has been duplicated on Emacs.
PPS.另请参阅为什么此函数每次都返回不同的值?对于相同的 Common Lisp 问题.
PPS. See also Why does this function return a different value every time? for an identical Common Lisp issue.
这篇关于在这种情况下,为什么 elisp 局部变量保持其值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!