奇怪的 Lisp 引用场景 - Graham 的 On Lisp,第 37 页
我正在阅读 Graham 的书“On Lisp”,但无法理解第 37 页的以下示例:
If we define exclaim so that its return value incorporates a quoted list, (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) could alter the list within the function: > (exclaim ’(fixnums and bignums and floats)) (FIXNUMS AND BIGNUMS AND FLOATS OH MY GOODNESS) To make exclaim proof against such problems, it should be written: (defun exclaim (expression) (append expression (list ’oh ’my)))
有人明白这里发生了什么吗?这严重破坏了我对引用的思维模式。
I'm working my way through Graham's book "On Lisp" and can't understand the following example at page 37:
If we define exclaim so that its return value incorporates a quoted list, (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) could alter the list within the function: > (exclaim ’(fixnums and bignums and floats)) (FIXNUMS AND BIGNUMS AND FLOATS OH MY GOODNESS) To make exclaim proof against such problems, it should be written: (defun exclaim (expression) (append expression (list ’oh ’my)))
Does anyone understand what's going on here? This is seriously screwing with my mental model of what quoting does.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
nconc 是一种破坏性操作,它通过更改其尾部来更改其第一个参数。在这种情况下,这意味着常量列表
'(oh my)
获得了一个新的尾部。希望能让这一点更清楚。有点像这样:
用
(list 'oh 'my)
替换'(oh my)
可以解决这个问题,因为不再有一个常量被所有人共享。每次调用exclaim
都会生成一个新列表(list
函数的目的是创建全新的列表)。nconc
is a destructive operation that alters its first argument by changing its tail. In this case, it means that the constant list'(oh my)
gets a new tail.To hopefully make this clearer. It's a bit like this:
Replacing
'(oh my)
with(list 'oh 'my)
fixes this because there is no longer a constant being shared by all and sundry. Each call toexclaim
generates a new list (thelist
function's purpose in life is to create brand new lists).观察到你的引用思维模型可能有缺陷,这是一个很好的观察——尽管它可能适用也可能不适用,具体取决于该思维模型是什么。
首先,请记住程序执行有多个阶段。 Lisp 环境必须首先将程序文本读入数据结构(列表、符号和各种文字数据,例如字符串和数字)。接下来,它可能会也可能不会将这些数据结构编译为机器代码或某种中间格式。最后,对生成的代码进行评估(当然,在机器代码的情况下,这可能只是意味着跳转到适当的地址)。
现在让我们把编译问题放在一边,重点关注读取和评估阶段,假设(为了简单起见)评估器的输入是读取器读取的数据结构列表。
考虑一种形式
(QUOTE x)
,其中 x 是对象的某种文本表示。这可能是(QUOTE ABC)
中的符号文字、(QUOTE (ABC))
中的列表文字、(QUOTE "abc) 中的字符串文字")
,或任何其他类型的文字。在阅读阶段,读者会将表单作为列表来阅读(称之为form1),其第一个元素是符号QUOTE
,第二个元素是对象 x',其文本表示为 x。请注意,我特别指出对象x'存储在表示表达式的列表中,即在某种意义上,它存储为的一部分代码本身。现在轮到评估员了。评估者的输入是form1,它是一个列表。因此,它查看 form1 的第一个元素,并确定它是符号
QUOTE
,它返回第二个元素作为评估结果列表。这是关键点。求值器返回要求值的列表的第二个元素,这是读者在第一个执行阶段(编译之前!)读入的内容。 这就是它所做的一切。它没有什么魔力,它非常简单,而且重要的是,没有创建新对象,也没有复制现有对象。因此,每当您修改“引用列表”时,您都在修改代码本身。自修改代码是一件非常令人困惑的事情,在这种情况下,行为实际上是未定义的(因为 ANSI Common Lisp 允许实现将代码放入只读内存中)。
当然,以上只是一个心理模型。实现可以自由地以各种方式实现模型,事实上,据我所知,Common Lisp 的实现没有像我的解释那样根本不进行编译。但这仍然是基本思想。
The observation that your mental model of quoting may be flawed is an excellent one—although it may or may not apply depending on what that mental model is.
First, remember that there are various stages to program execution. A Lisp environment must first read the program text into data structures (lists, symbols, and various literal data such as strings and numbers). Next, it may or may not compile those data structures into machine code or some sort of intermediary format. Finally, the resulting code is evaluated (in the case of machine code, of course, this may simply mean jumping to the appropriate address).
Let's put the issue of compilation aside for now and focus on the reading and evaluation stages, assuming (for simplicity) that the evaluator's input is the list of data structures read by the reader.
Consider a form
(QUOTE x)
where x is some textual representation of an object. This may be symbol literal as in(QUOTE ABC)
, a list literal as in(QUOTE (A B C))
, a string literal as in(QUOTE "abc")
, or any other kind of literal. In the reading stage, the reader will read the form as a list (call it form1) whose first element is the symbolQUOTE
and whose second element is the object x' whose textual representation is x. Note that I'm specifically saying that the object x' is stored within the list that represents the expression, i.e. in a sense, it's stored as a part of the code itself.Now it's the evaluator's turn. The evaluator's input is form1, which is a list. So it looks at the first element of form1, and, having determined that it is the symbol
QUOTE
, it returns as the result of the evaluation the second element of the list. This is the crucial point. The evaluator returns the second element of the list to be evaluated, which is what the reader read in in the first execution stage (prior to compilation!). That's all it does. There's no magic to it, it's very simple, and significantly, no new objects are created and no existing ones are copied.Therefore, whenever you modify a “quoted list”, you're modifying the code itself. Self-modifying code is a very confusing thing, and in this case, the behaviour is actually undefined (because ANSI Common Lisp permits implementations to put code in read-only memory).
Of course, the above is merely a mental model. Implementations are free to implement the model in various ways, and in fact, I know of no implementation of Common Lisp that, like my explanation, does no compilation at all. Still, this is the basic idea.
在 Common Lisp 中。
请记住:
以上是文字列表。 恒定数据。
LIST 是一个函数,当调用时返回一个新列表,其参数作为列表元素。
避免修改文字列表。效果没有标准化。想象一下 Lisp 将所有常量数据编译到只读内存区域。想象一下 Lisp 获取常量列表并在函数之间共享它们。
Lisp 编译器可以创建一个由两个函数共享的列表。
如果您修改函数 a 返回的列表,
实现可以自由地做他们喜欢做的事情。这为优化留下了空间。
In Common Lisp.
Remember:
Above is a literal list. Constant data.
LIST is a function that when call returns a fresh new list with its arguments as list elements.
Avoid modifying literal lists. The effects are not standardized. Imagine a Lisp that compiles all constant data into a read only memory area. Imagine a Lisp that takes constant lists and shares them across functions.
A Lisp compiler may create one list that is shared by both functions.
If you modify the list returned by function a
Implementations have the freedom to do what they like. This leaves room for optimizations.