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

时间:2015-04-26 23:29:30

标签: lisp common-lisp literals

首先,让我说我是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),那么我必须至少部分错误。

2 个答案:

答案 0 :(得分:5)

评估时,'(nil)和(list nil)产生类似的列表,但前者在源代码中存在时可视为常量。您不应对Common Lisp中的常量引用列表执行任何破坏性操作。请参阅http://l1sp.org/cl/3.2.2.3http://l1sp.org/cl/quote。特别是,后者说“如果文字对象(包括被引用的对象)被破坏性地修改,则后果是不确定的。”

答案 1 :(得分:3)

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

(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)
    
  4. 事实上。最后两个行为可能发生在依赖于正在编译的函数的相同实现中。

    在CLISP中,两个函数的工作方式相同。我还看到在与SBCL一起拆解时,常量实际上已经发生了变异,所以我想知道它是否在编译时经常折叠(cdr '(0))并且根本不使用变异列表。这真的无关紧要,因为两者都被认为是好的"未定义"行为。

    关于此问题的from CLHS部分很短

      

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