为什么我不能在 Common Lisp 的 REPL 中 (push 3 '())?

Why I can't (push 3 '()) in Common Lisp's REPL?

我在 Emacs 中使用 Common Lisp 和 Slime。我对避免变异的经验比使用它更多。

在REPL中我可以做的:

CL-USER> (defvar so-example '())
SO-EXAMPLE

CL-USER> (push 7 so-example)
(7)

并且有效。但是,如果我尝试:

CL-USER> (push 7 '())

我收到一条错误消息:

Undefined function: (SETF QUOTE)

好的。由于quote是一个问题,我也试过:

CL-USER> (push 7 nil)

这也会引发错误消息:

NIL is a constant and thus can't be set.

为什么会这样?为什么说得通?

对我来说,这很奇怪。

修改文字常量会导致 Common Lisp 中出现未定义的行为; from the HyperSpec:

The consequences are undefined if literal objects (including quoted objects) are destructively modified.

这解释了使用 (push 7 nil) 观察到的错误消息; (push 7 ()).

会出现相同的错误消息

表达式(push 7 '())等价于(setf '() (cons 7 '()))setf 需要 place,但 (quote ()) 不是一个地方。 setf 宏在意识到 quote 无法生成可设置的位置时正在捕获错误。

请注意 according to the documentationpush 需要 itemplace(不是 list): "push 将项目添加到存储在适当位置的列表中...."。也就是说,地方 是对列表的引用,而不是列表本身。

一般来说,像这样尝试改变文字常量没有多大意义。考虑用数字来做这件事。试图通过修改数字 7 将 7 更改为 8 不是我们想要做的。相反,我们将建立一个 binding 到 7,并改变绑定。对空列表做同样的事情:

CL-USER> (defvar empty-list '())
SO-EXAMPLE
CL-USER> (setf empty-list (cons 7 empty-list))
(7)

此处建立了到空列表的绑定 (empty-list),然后 绑定 发生变异,将 empty-list 绑定到创建的新列表将 7 转换为 ()。这正是第一个发布的有效示例中发生的事情。

CL-USER> (defvar so-example '())
SO-EXAMPLE
CL-USER> (push 7 so-example)
(7)

使用算术的类似示例可能是:

CL-USER> (defvar x 7)
X
CL-USER> (setf x (+ 1 x))
8
CL-USER> x
8

然而你可能不会期望 (setf 7 (+ 1 7)) 做任何好事。

push 不适用于文字作为第二个参数,因为它应该改变第二个参数指示的位置。空列表没有位置,不能重新定义。

push 是一个宏,它为看起来不同的第二个参数做不同的事情。您可以通过查看 maroexpand-1:

来检查它的作用
(macroexpand-1 '(push 5 binding))
; ==> (set1 binding (cons 5 binding))

(macroexpand-1 '(push 5 (car structure)))
; ==> (let* ((tmp structure))
;       (rplaca tmp (cons 5 (car tmp))))

它如何知道要做什么是通过查看 setf 函数。例如。呼叫:

(get-setf-expansion 'binding)
; ==> () 
;     ()
;     (tmp)
;     (setq binding tmp)
;     binding

您可以定义自己的,例如。 a class 这样你就可以将 push 个元素添加到你创建的 class 的对象中,并让它做一些特别的事情。例如。您正在扩展 push 的语言功能,这样它也支持您制作的 class。

那么当我计算 (get-setf-expansion ''()) 时会发生什么 你明白为什么 (macroexpand-1 '(push 3 '()) 的扩展变成了:

(let* ((tmp (cons 3 '())))
  (funcall #'(setf quote) tmp '())) 
 

还有你异常错误消息的来源。

PUSH 的文档说:

push item place => new-place-value

地方广义引用:变量、数组槽、结构槽、CLOS对象槽等等。它们记录在此处:CLHS 5.1 Generalized Reference

广义是什么意思?在 Common Lisp 中,places 是一个超越变量或槽等简单引用的概念。它甚至是用户可扩展的 -> 可以定义新类型的 places.

NIL 作为一个地方

NIL(与 () 相同)记录为 常量变量.

因此这样的事情失败了:

(setf nil 10)

(push 10 nil) 正试图将 10 推到 位置 NIL.

CL-USER 1 > (macroexpand-1 '(push 10 nil))
(LET ((#:|new-value-1070| 10))
  (LET* ((#:|Store-Var-1069| (CONS #:|new-value-1070| NIL)))
    (SETQ NIL #:|Store-Var-1069|)))

曾经有过:尝试将 NIL(作为参考,这里是一个变量)设置为值 10。

由于NIL被定义为常量变量并且常量变量不可更改,所以不能把东西推到 nil.

的地方

因此NIL不是一个有用的地方

'NIL 作为一个地方

为什么 (push 10 '()) 失败了?这与 (push 10 'nil)(push 10 (quote nil)).

相同

同样,push 需要一个位置。 (quote ...) 不是定义的地方。

这里定义了默认定义的地方:CLHS 5.1.2 Kinds of Places.

这是有道理的,因为 '() 没有引用任何东西。请记住:地点广义引用。它应该是一个文字对象 ().

函数缺点

如果我们想向列表中添加一个新对象,那么我们使用cons:

CL-USER 4 > (cons 10 nil)
(10)

CL-USER 5 > (cons 10 'nil)
(10)

必须使用结果列表:将其存储在某处,将其传递给函数,...