什么';(列表为零)和';(无)在Lisp中?

什么';(列表为零)和';(无)在Lisp中?,lisp,common-lisp,literals,Lisp,Common Lisp,Literals,首先,让我说我是Lisp的初学者。老实说,我已经是初学者一段时间了,但还有很多事情我不太清楚 在我写作的时候,我在代码中发现了一个奇怪的bug 下面是一个函数,它将返回列表(0 1…n),并附加列表e。它使用rplacd跟踪最后一个元素,以避免最后调用last 例如,(foo4'(x))返回(01234x) “head”存储在a中,它不是简单的nil,因为只有一个nil,而且从来没有它的副本(如果我理解正确),因此我不能简单地附加到nil (defun foo (n e) (let* (

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

在我写作的时候,我在代码中发现了一个奇怪的bug

下面是一个函数,它将返回列表
(0 1…n)
,并附加列表
e
。它使用
rplacd
跟踪最后一个元素,以避免最后调用
last

例如,
(foo4'(x))
返回
(01234x)

“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)))))
这些函数之间的唯一区别是
栏中的
(列表无)
(无)
替换。当
foo
按预期工作时,
bar
始终返回
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)))))

我最初猜测这是因为
a
的原始
cdr
确实是
nil
,并且引用的列表可能被认为是常量。然而,如果我做了
(setfx'(nil))(rplacdx1)
我得到了预期的
(nil.1)
,所以我至少部分错了。

当计算时,“(nil)和(list nil)产生了类似的列表,但前者在源代码中出现时可以被认为是常量。您不应该对Common Lisp中的常量引用列表执行任何破坏性操作。见和。特别是,后者说“如果文字对象(包括引用的对象)被破坏性地修改,后果是不确定的。”

引用的数据被认为是一个常量。如果您有两个功能:

(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
    相同的列表(读者可能会这样做)

  • 实施过程中可以看到它是相同的,hench节省了空间:

    (test2) ; ==> (0)
    (test)  ; ==> 1
    (test)  ; ==> 2
    (test)  ; ==> 3
    (test2) ; ==> (3)
    
  • 事实上。后两种行为可能发生在同一个实现中,具体取决于正在编译的函数与否

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

    关于这一点的部分很短

    如果文字对象(包括带引号的 对象)进行破坏性修改


    看起来
    条的括号不平衡。@user2357112已更正。缺少的结束参数正好在
    '(nil)
    之后。很抱歉,我不熟悉Common Lisp,但我相信Scheme,任何引用了
    的内容都应该被视为不可变的。这在公共口齿不清中适用吗?看起来你在试图改变这个东西。@user2357112是的,我从我过去使用Scheme的经验中记得这一点。在Common Lisp中似乎不一样。但我很谨慎,因为我怀疑我的bug与此有关。我的另一个猜测是,在一个版本中,您将得到一个带有符号
    nil
    的列表,而在另一个版本中,
    nil
    将计算为空列表。编辑:不,看起来符号
    nil
    的计算结果是符号
    nil
    。这很有意义,谢谢。所以,可以肯定的是,如果
    (setf x'(nil))(rplacd x 1)
    做了我错误地期望的事情,这只会归功于未定义的后果?这里的方案更友好,因为它会通知错误IIRW。当SBCL能够检测到常量数据的修改时,它将发出有用的警告并引用标准的适用位。(我就是这样查找我的回复的参考资料的。)但它还不能检测到所有情况。在那之前,你只需要知道规则。@Jean-ClaudeArbaut:一个旧的常见问题解答说:R5RS的第3.4节指出,试图修改不可变对象(如文字列表)是错误的。不过,并不是所有方案都将此报告为错误,而是修改文字列表。谢谢。可以肯定的是,如果我复制一个带引号的列表,副本是可变的,对吗?@Jean-ClaudeArbaut是的,但只有顶层,因为
    副本列表
    是浅副本。因此,它不会像以前那样递归地复制列表中的列表元素。还有一个问题:在for ncoc函数中,有一个例子:(setq x'(abc))(setq y'(def))(ncoc x y)这是不是错了?我的意思是,x和y都有一个临时列表,x由NCOC修改。