为什么elisp局部变量在这种情况下保持其值?

abo*_*abo 16 lisp emacs elisp literals

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

(defun test-a ()
  (let ((x '(nil)))
    (setcar x (cons 1 (car x)))
    x))
Run Code Online (Sandbox Code Playgroud)

(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)))))
Run Code Online (Sandbox Code Playgroud)

(test-b)总是回来((1)).为什么不test-atest-b等同?

sds*_*sds 22

test-a自修改代码.这非常危险.当变量 xlet表单的末尾消失时,其初始值将保留在函数对象中,这就是您要修改的值.请记住,在Lisp中,函数是一个第一类对象,可以传递(就像数字或列表一样),有时也会被修改.这正是你在这里所做的:初始值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))
Run Code Online (Sandbox Code Playgroud)

好的

test-b返回一个新的利弊细胞,因此是安全的.x永远不会修改初始值.之间的差(setcar x ...)(setq x ...)是,前者修改对象已经存储在变量x而后者存储一个的对象x.所不同的是类似x.setField(42)x = new MyObject(42)C++.

底线

在一般情况下,最好是把报价数据等'(1)作为常数-也没有对其进行修改:

quote返回参数,而不进行评估. (quote x)收益率x. 警告:quote不构造其返回值,而只返回由Lisp读取器预先构造的值(请参阅信息节点 Printed Representation).这意味着(a . b)不同于(cons 'a 'b):前者不利.除非您喜欢自修改代码,否则应该为永远不会被副作用修改的常量保留引用.请参阅信息节点重新排列中的常见缺陷,以获取修改带引号的对象时意外结果的示例.

如果需要修改的列表,以创建listconscopy-list代替quote.

查看更多 示例.

PS.这在Emacs上已经重复了.

PPS.另请参见为什么此函数每次都返回不同的值?对于相同的Common Lisp问题.