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-a和test-b等同?
sds*_*sds 22
test-a是自修改代码.这非常危险.当变量 x在let表单的末尾消失时,其初始值将保留在函数对象中,这就是您要修改的值.请记住,在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):前者不利.除非您喜欢自修改代码,否则应该为永远不会被副作用修改的常量保留引用.请参阅信息节点重新排列中的常见缺陷,以获取修改带引号的对象时意外结果的示例.
如果需要修改的列表,以创建list或cons或copy-list代替quote.
PS.这在Emacs上已经重复了.
PPS.另请参见为什么此函数每次都返回不同的值?对于相同的Common Lisp问题.