且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-06-23 23:11:07

The Bad

test-a自修改代码.这极其危险.虽然变量 xlet 形式的末尾消失了,但它的初始值 仍然存在于函数对象中,这就是您正在修改的值.请记住,在 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) 相同:前者没有缺点.引用应该保留给永远不会被副作用修改的常量,除非你喜欢自修改代码.查看信息中的常见陷阱节点 重排 意外结果示例什么时候引用的对象被修改.

quote returns the argument, without evaluating it. (quote x) yields x. Warning: quote does not construct its return value, but just returns the value that was pre-constructed by the Lisp reader (see info node Printed Representation). This means that (a . b) is not identical to (cons 'a 'b): the former does not cons. Quoting should be reserved for constants that will never be modified by side-effects, unless you like self-modifying code. See the common pitfall in info node Rearrangement for an example of unexpected results when a quoted object is modified.

如果您需要修改列表,使用 listconscopy-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.