首先,让我说我是Lisp的初学者。说实话,我已经有一段时间了,但是还有很多我不太了解的事情。
在我撰写this question时,我在代码中想出了一个奇怪的错误。
这是一个函数,它将返回列表(0 1 ... n)
并附加列表e
。它沿途使用rplacd
来跟踪最后一个元素,以避免最后一次调用last
。
例如,(foo 4 '(x))
会返回(0 1 2 3 4 x)
。
“head”存储在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)))))
这些功能之间的唯一区别是(list nil)
替换为'(nil)
中的bar
。虽然foo
按预期工作,但bar
始终返回nil
。
我的初步猜测是因为cdr
的原始a
确实是nil
,所以引用列表可能会被视为常量。但是,如果我(setf x '(nil)) (rplacd x 1)
我按预期得到(nil . 1)
,那么我必须至少部分错误。
答案 0 :(得分:5)
评估时,'(nil)和(list nil)产生类似的列表,但前者在源代码中存在时可视为常量。您不应对Common Lisp中的常量引用列表执行任何破坏性操作。请参阅http://l1sp.org/cl/3.2.2.3和http://l1sp.org/cl/quote。特别是,后者说“如果文字对象(包括被引用的对象)被破坏性地修改,则后果是不确定的。”
答案 1 :(得分:3)
引用数据被视为常量。如果您有两个功能:
(defun test (&optional (arg '(0)))
(setf (car arg) (1+ (car arg)))
(car arg))
(defun test2 ()
'(0))
这两个函数都使用常量列表(0)
对吗?
实现可以选择不改变常量:
(test) ; ==> Error, into the debugger we go
实现可以cons
两次相同的列表(读者也可以这样做)
(test2) ; ==> (0)
(test) ; ==> 1
(test) ; ==> 2
(test) ; ==> 3
(test2) ; ==> (0)
实施可以看到它是相同的并且可以节省空间:
(test2) ; ==> (0)
(test) ; ==> 1
(test) ; ==> 2
(test) ; ==> 3
(test2) ; ==> (3)
事实上。最后两个行为可能发生在依赖于正在编译的函数的相同实现中。
在CLISP中,两个函数的工作方式相同。我还看到在与SBCL一起拆解时,常量实际上已经发生了变异,所以我想知道它是否在编译时经常折叠(cdr '(0))
并且根本不使用变异列表。这真的无关紧要,因为两者都被认为是好的"未定义"行为。
关于此问题的from CLHS部分很短
如果文字对象(包括引用的话),后果是不确定的 对象进行破坏性修改。