如何解决为什么我不能 (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 documentation、push
需要一个 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 举报,一经查实,本站将立刻删除。