Emacs 在这种情况下,为什么elisp局部变量保持其值?
有人能给我解释一下这个非常简单的代码片段中发生了什么吗Emacs 在这种情况下,为什么elisp局部变量保持其值?,emacs,lisp,elisp,literals,Emacs,Lisp,Elisp,Literals,有人能给我解释一下这个非常简单的代码片段中发生了什么吗 (defun test-a () (let ((x '(nil))) (setcar x (cons 1 (car x))) x)) 在第一次调用(test-a)时,我得到了预期的结果:((1))。 但令我惊讶的是,再次调用它,我得到了((11)),((11))等等。 为什么会这样?我期望(test-a)总是返回((1)),这是错误的吗? 还要注意的是,在重新评估test-a的定义后,返回结果将重置 也考虑到这个函数和
(defun test-a ()
(let ((x '(nil)))
(setcar x (cons 1 (car x)))
x))
在第一次调用(test-a)
时,我得到了预期的结果:((1))
。
但令我惊讶的是,再次调用它,我得到了((11))
,((11))
等等。
为什么会这样?我期望(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
不相等?看起来(let)中的“(nil)只计算一次。当您(设置CAR)时,每个调用都在修改相同的列表。如果您将“(nil)替换为(list(list)),则可以使(test-a)工作,尽管我认为有一种更优雅的方法可以做到这一点
(test-b)每次都从cons单元格构造一个全新的列表,这就是它工作方式不同的原因。坏消息
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))
好人
test-b
返回一个新的cons单元格,因此是安全的。x
的初始值永远不会修改。(setcar x…
和(setq x…
之间的区别在于前者修改变量x
中已存储的对象,而后者在x
中存储新的对象。这种差异与C++
中的x.setField(42)
与x=newmyobject(42)
类似
底线
一般来说,最好将数据(如”(1)
视为常量-不要修改它们:
quote
返回参数,不计算它<代码>(引用x)产生x
。
警告:引号
不构造其返回值,只返回
Lisp读取器预先构造的值(请参见信息节点
)。这意味着(a.b)
不是
与(cons'a'b)
相同:前者不包含cons。引用应该
为永远不会被副作用修改的常数保留,
除非你喜欢自我修改代码。查看信息中的常见陷阱
节点以获取以下情况下意外结果的示例:
引用的对象被修改
如果需要,请使用list
或cons
或copy list
而不是quote
创建它
看
另外,这已经在上复制了
PPS。另请参见相同的常见Lisp问题。我发现罪魁祸首确实是“引用”。下面是它的文档字符串: 返回参数,但不计算它 警告:'quote'不构造其返回值,只返回值 Lisp读取器预先构造的值 应为将 永远不要被副作用修改,除非你喜欢自我修改代码。 为了方便,我也重写了
(setq test-a
(lambda () ((lambda (x) (setcar x (cons 1 (car x))) x) (quote (nil)))))
然后用
(funcall test-a)
看看“测试-a”是如何变化的。谢谢,你的回答确实提供了一个解决办法,但我仍然不明白为什么会发生这种情况。x inside’test-a被声明为本地的,实际上它在函数外部是不可见的,但是为什么函数在调用之间保留关于x的信息呢?我猜,这是因为emacs在解析let中的文本时构造了列表,然后每次调用函数时都引用相同的列表。这就像是在使用指向解析树的指针来引用x一样。@abo abo:emacs不保留关于x的信息,而是保留关于x的初始值的信息。看我的答案。但是为什么修改会持续?变量x,我认为它只是一个指向
'(nil)
的指针,不应该在let表单的末尾被删除吗?@Tyler:变量被删除了,但初始值没有。请参阅编辑。既然您已经解释过了,这就有道理了。我永远不会建立这种连接。在定义test-a
之后调用(符号函数'test-a)
,以及在调用它之后再次调用都很有启发性。@abo abo在test-a中,setcar用于直接修改x指向的列表。X指向函数定义中的一个常量,因此函数本身会发生更改。在测试b中,x最初指向的列表中的值用于构造要分配给x的新列表。初始值永远不会直接改变。所以你是对的,赛卡是关键的区别。