为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

慕娘9325324 2019-11-19 14:41:40

有人可以向我解释一下这个非常简单的代码段中的情况吗?


(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,返回结果将重置。


还请考虑此功能按我的预期工作:


(defun test-b ()

  (let ((x '(nil)))

    (setq x (cons (cons 1 (car x)) 

                  (cdr x)))))

(test-b)总是返回((1))。为什么不test-a和test-b等同?


查看完整描述

3 回答

?
aluckdog

坏人

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。差异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问题。


查看完整回答
反对 回复 2019-11-19
?
MMTTMM

我发现罪魁祸首的确是“引用”。这是它的文档字符串:


返回参数,不对其求值。


...


警告:`quote'不会构造其返回值,而只是返回由Lisp读取器预先构造的值


...


报价应保留给不会被副作用修改的常量,除非您喜欢自修改代码。


为了方便我也改写了


(setq test-a 

      (lambda () ((lambda (x) (setcar x (cons 1 (car x))) x) (quote (nil)))))

然后用


(funcall test-a)

看看'test-a是如何变化的。


查看完整回答
反对 回复 2019-11-19
?
手掌心

看起来您的(let)中的'(nil)仅被评估一次。当您(设置汽车)时,每个调用都就地修改了相同的列表。如果我用(list(list))代替'(nil),则可以使(test-a)工作,尽管我认为有一种更优雅的方法。


(test-b)每次都从cons单元构建一个全新的列表,这就是为什么它工作原理不同的原因。


查看完整回答
反对 回复 2019-11-19

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信