Lisp中的(list nil)和'(nil)有什么区别?



首先,让我说我是Lisp的初学者。老实说,我已经入门一段时间了,但还有很多事情我不太了解。

在我写这个问题的时候,我在代码中发现了一个奇怪的错误。

这里有一个函数,它将返回附加了列表e的列表(0 1 ... n)。它使用rplacd来跟踪最后一个元素,以避免最后调用last

例如,(foo 4 '(x))返回(0 1 2 3 4 x)

"头"存储在a中,而不是简单的nil,因为只有一个nil,而从来没有它的副本(如果我理解正确的话),因此我不能简单地附加到nil

(defun foo (n e)
(let* ((a (list nil)) (tail a))
(loop for i to n
do (rplacd tail (setf tail (list i)))
finally (rplacd tail (setf tail e))
(return (cdr a)))))
(defun bar (n e)
(let* ((a '(nil)) (tail a))
(loop for i to n
do (rplacd tail (setf tail (list i)))
finally (rplacd tail (setf tail e))
(return (cdr a)))))

这些功能之间的唯一区别是bar中的'(nil)取代了(list nil)。当foo按预期工作时,bar总是返回nil

我最初的猜测是,发生这种情况是因为a的原始cdr确实是nil,并且引用的列表可以被认为是常数。然而,如果我做(setf x '(nil)) (rplacd x 1),我会得到预期的(nil . 1),所以我一定至少部分错了。

计算时,'(nil)和(list-nil)生成类似的列表,但前者在源代码中存在时可以被视为常量。您不应该对Common Lisp中的常量引用列表执行任何破坏性操作。看见http://l1sp.org/cl/3.2.2.3和http://l1sp.org/cl/quote.特别是,后者说:"如果文字对象(包括引用的对象)被破坏性修改,后果是不确定的。">

引用的数据被视为常量。如果你有两个功能:

(defun test (&optional (arg '(0)))
(setf (car arg) (1+ (car arg)))
(car arg))
(defun test2 ()
'(0))

这两个函数都使用常量列表(0),对吗?

  1. 实现可以选择不更改常量:

    (test) ; ==> Error, into the debugger we go
    
  2. 实现可以两次cons相同的列表(读者可能会为其这样做)

    (test2) ; ==> (0)
    (test)  ; ==> 1
    (test)  ; ==> 2
    (test)  ; ==> 3
    (test2) ; ==> (0)
    
  3. 实现可以看到它是一样的,并节省空间:

    (test2) ; ==> (0)
    (test)  ; ==> 1
    (test)  ; ==> 2
    (test)  ; ==> 3
    (test2) ; ==> (3)
    

事实上。最后两种行为可能发生在同一个实现中,这取决于是否编译函数。

在CLISP中,两个功能的工作原理相同。我还看到,当使用SBCL进行反汇编时,常数实际上是突变的,所以我想知道它在编译时是否有常数折叠的(cdr '(0)),并且根本不使用突变列表。这其实并不重要,因为两者都被认为是良好的"未定义"行为。

CLHS关于这一点的部分是非常短的

如果文字对象(包括引号对象)被破坏性地修改。

最新更新