微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

我在 Emacs 中使用 Common Lisp 和 Slime。与使用突变相比,我有更多避免突变的经验。

在 REPL 中我可以:

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

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

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

CL-USER> (push 7 '())

我收到一条错误消息:

未定义函数:(SETF QUOTE)

好的。由于 quote 有问题,我也试过:

CL-USER> (push 7 nil)

这也会引发错误消息:

NIL 是一个常量,因此不能设置。

为什么会这样?为什么有意义?

对我来说,这很奇怪。

解决方法

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

如果文字对象(包括引用对象)被破坏性地修改,后果是不确定的。

这解释了使用 (push 7 nil) 观察到的错误消息;相同的错误消息会伴随 (push 7 ())

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

请注意,according to the documentationpush 需要一个 item 和一个 place(不是 list):“ push 将 item 附加到存储到位的列表中......”。也就是说,place 是对列表的引用,而不是列表本身。

一般来说,尝试改变这样的字面常量没有多大意义。考虑用数字来做这件事。试图通过修改数字 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 绑定到通过 consing {{1} 创建的新列表} 到 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 做任何好事。

,

PUSH 的文档说:

push item place => new-place-value

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

广义是什么意思?在 Common Lisp 中,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 被定义为一个常量变量,并且常量变量是不可改变的,所以我们不能把东西推到{{1 }}。

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

'NIL 作为一个地方

为什么 NIL 会失败?与 (push 10 '())(push 10 'nil) 相同。

同样,(push 10 (quote nil)) 需要一个位置。 push 不是一个明确的地点。

默认定义的位置在此处定义:CLHS 5.1.2 Kinds of Places

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

功能缺点

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

cons

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

,

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

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

那么当我评估 (get-setf-expansion ''()) 时会发生什么,你就会明白 (macroexpand-1 '(push 3 '()) 的展开变成的原因:

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

这就是您异常错误消息的来源。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。