13

我正在阅读格雷厄姆的书“On Lisp”,但无法理解第 37 页的以下示例:

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

(defun exclaim (表达式)
  (附加表达式'(哦,我的)))

> (惊呼'(狮子和老虎和熊))
(狮子、老虎和熊 OH MY)
> (nconc * '(善良))
(狮子、老虎和熊哦,天哪)

可以改变函数内的列表:

> (exclaim '(fixnums and bignums and floats))
(FIXNUMS 和 BIGNUMS 和浮动哦,我的天哪)

为了对这些问题做出惊呼证明,应该写成:
(defun exclaim (表达式)
  (追加表达式 (list 'oh 'my)))

有谁明白这里发生了什么?这严重破坏了我对引用所做的心理模型。

4

3 回答 3

7

观察到你的引用心智模型可能有缺陷是一个很好的观察——尽管它可能适用也可能不适用,这取决于那个心智模型是什么。

首先,请记住程序执行有不同的阶段。Lisp 环境必须首先将程序文本入数据结构(列表、符号和各种文字数据,如字符串和数字)。接下来,它可能会也可能不会将这些数据结构编译成机器代码或某种中间格式。最后,评估结果代码(当然,在机器代码的情况下,这可能只是意味着跳转到适当的地址)。

让我们暂时把编译的问题放在一边,专注于阅读和评估阶段,假设(为简单起见)评估者的输入是读者读取的数据结构列表。

考虑一种形式(QUOTE x),其中x是对象的某种文本表示。这可以是 in 中的符号文字、 in(QUOTE ABC)中的列表文字、 in(QUOTE (A B C))中的字符串文字(QUOTE "abc")或任何其他类型的文字。在阅读阶段,读者会将表单读取为一个列表(称为form1),其第一个元素是符号QUOTE,第二个元素是对象x',其文本表示为x。请注意,我是说对象x'存储在表示表达式的列表中,即在某种意义上,它存储为代码本身的一部分

现在轮到评估者了。评估者的输入是form1,它是一个列表。因此,它查看form1的第一个元素,并在确定它是 symbolQUOTE后,返回 list 的第二个元素作为评估结果。这是关键点。评估器返回要评估的列表的第二个元素,这是读者在第一个执行阶段(编译之前!)读入的内容。 这就是它所做的一切。 它没有魔法,它非常简单,而且重要的是,不会创建新对象,也不会复制现有对象。

因此,每当您修改“引用列表”时,您就是在修改代码本身。自修改代码是一件非常令人困惑的事情,在这种情况下,行为实际上是未定义的(因为 ANSI Common Lisp 允许实现将代码放入只读内存中)。

当然,以上只是一个心智模型。实现可以自由地以各种方式实现模型,事实上,我知道没有任何 Common Lisp 实现,就像我的解释一样,根本不进行编译。不过,这是基本思想。

于 2010-10-23T14:37:11.253 回答
6

nconc是一种破坏性操作,通过改变它的尾巴来改变它的第一个参数。在这种情况下,这意味着常量列表'(oh my)得到了一个新的尾部。

希望能更清楚地说明这一点。有点像这样:

; Hidden variable inside exclaim
oh_my = oh → my → nil

(exclaim '(lions and tigers and bears)) =
    lions → and → tigers → and → bears → oh_my

(nconc * '(goodness)) destructively appends goodness to the last result:
    lions → and → tigers → and → bears → oh → my → goodness → nil
so now, oh_my = oh → my → goodness → nil

替换'(oh my)(list 'oh 'my)修复此问题,因为不再有一个常量被所有人共享。每次调用都会exclaim生成一个新列表(该list函数的实际用途是创建全新的列表)。

于 2010-10-23T08:34:23.017 回答
6

在 Common Lisp 中。

记住:

'(1 2 3 4)

上面是一个字面列表常数数据

(list 1 2 3 4)

LIST 是一个函数,它在调用时返回一个全新的列表,其参数作为列表元素。

避免修改文字列表。效果没有标准化。想象一个将所有常量数据编译到只写内存区域的 Lisp。想象一个 Lisp,它接受常量列表并在函数之间共享它们。

(defun a () '(1 2 3)

(defun b () '(1 2 3))

Lisp 编译器可以创建一个由两个函数共享的列表。

如果修改函数 a 返回的列表

  • 它可能不会改变
  • 它可能会改变
  • 这可能是一个错误
  • 它还可能更改函数 b 返回的列表

实现可以自由地做他们喜欢的事情。这为优化留下了空间。

于 2010-10-23T10:49:17.847 回答