来自《On Lisp》的奇怪引用列表示例

13

这段来自于《On Lisp》的内容真的很令人困惑——不清楚返回一个类似于'(oh my)的引用列表实际上如何能够改变函数在未来的行为方式:下一次调用该函数时,返回的列表难道不会再次从头开始生成吗?

如果我们定义exclaim,使其返回值包含了一个引用列表,

(defun exclaim (expression) 
  (append expression ’(oh my)))

那么返回值的任何后续破坏性修改

(exclaim ’(lions and tigers and bears)) 
->  (LIONS AND TIGERS AND BEARS OH MY)
(nconc * ’(goodness))
->  (LIONS AND TIGERS AND BEARS OH MY GOODNESS)

可以在函数内更改列表:

(exclaim ’(fixnums and bignums and floats)) 
->  (FIXNUMS AND BIGNUMS AND FLOATS OH MY GOODNESS)

为了使表达对这些问题具有免疫力,应该这样写:

(defun exclaim (expression)
  (append expression (list ’oh ’my)))
那最后一次调用exclaim是如何把单词goodness添加到结果中的?该函数没有引用任何外部变量,所以独立调用 nconc 如何实际改变了 exclaim 函数的工作方式?答案就在于nconc修改了words的值,这也影响到了exclaim函数。

我看到你已经有了一个答案,但是请查看这个答案以获取更多讨论和资源。 - Joshua Taylor
1
此外,这可能是 Strange Lisp Quoting scenario - Graham's On Lisp, page 37 的重复内容。 - Joshua Taylor
1个回答

14

a) 在Common Lisp标准中,修改字面值列表的影响是未定义的。这里展示的示例是一种可能的行为。

(1 2 3 4)是一个字面列表。但像(list 1 2 3 4)这样的LIST调用会在运行时返回一个新的consed列表。

b) 列表是函数代码中的字面数据。每次调用都将返回完全相同的数据对象。如果您想在每次调用时提供新列表,则需要使用类似于LIST或COPY-LIST的东西。

c) 由于返回的列表始终是相同的字面数据对象,因此修改它可能会产生上述效果。人们还可以想象,如果将代码及其对象分配在只读内存中,则会发生错误。然后修改列表将尝试写入只读内存。

d) 在处理源代码中的文字列表数据时要记住一件事:Lisp编译器可以自由地优化存储。如果一个列表在源代码中出现多次,编译器可以检测到这一点,并且只创建一个列表。所有不同的位置都指向这个列表。因此,修改列表将具有这样的效果,即这些更改可能在多个位置可见。

这也可能发生在其他文字数据对象(如数组/向量)上。

如果您的数据结构是代码的一部分,则返回此内部数据结构,修改此数据结构-则尝试修改您的代码。

还要注意,Lisp可以通过解释器执行。解释器通常在Lisp源代码结构上工作-代码不是机器代码,而是解释为Lisp数据的解释器Lisp代码。在这里,您可能能够在运行时修改源代码,而不仅仅是源代码中嵌入的数据。


1
所以你的意思是,'(oh my) 在函数返回后仍然存在,并且作为一个实际的持久数据存在,而 nconc 正在改变这个数据并使其变为 '(oh my goodness),这样当 exclaim 再次运行时,它使用的是该数据中的新值? - johnbakers
1
是的。exclaim本身并不会改变任何东西,这是由nconc完成的。exclaim仅仅使修改后的引用列表的新内容可见。 - gsg
1
@Rainer:这个(a)是针对所有列表还是只有字面列表? - Baggers
1
@Baggers:只有文字列表。我改了措辞,使之清晰明了——没有上下文的背景。谢谢! - Rainer Joswig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接