【问题标题】:What's the difference between (list nil) and '(nil) in Lisp? [duplicate]Lisp 中的 (list nil) 和 '(nil) 有什么区别? [复制]
【发布时间】:2015-07-05 06:21:41
【问题描述】:

首先,让我说我是 Lisp 的初学者。说实话,我初学者也有一段时间了,但还是有很多东西不是很了解。

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

这是一个函数,它将返回列表(0 1 ... n),并附加列表e。它一路使用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 中的(list nil) 替换为'(nil)。虽然 foo 按预期工作,但 bar 始终返回 nil

我最初的猜测是发生这种情况是因为a 的原始cdr 确实是nil,并且引用的列表可能被认为是不变的。但是,如果我执行(setf x '(nil)) (rplacd x 1),我会按预期得到(nil . 1),所以我必须至少部分错误。

【问题讨论】:

  • bar 的括号似乎不平衡。
  • @user2357112 已更正。缺少的结束括号就在'(nil) 之后。对此感到抱歉。
  • 我不熟悉Common Lisp,但我相信Scheme,任何用' 引用的东西都应该被视为不可变的。这适用于 Common Lisp 吗?看起来你正试图改变这个东西。
  • @user2357112 是的,我从过去使用 Scheme 的经验中记得这一点。在 Common Lisp 中,它似乎不一样。但我很谨慎,因为我怀疑我的错误与此有关。
  • 我的另一个猜测是,在一个版本中,您将获得一个带有符号 nil 的列表,而在另一个版本中,nil 被评估为一个空列表。编辑:不,看起来符号 nil 计算结果为符号 nil

标签: lisp common-lisp literals


【解决方案1】:

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

(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)) 并且根本不使用变异列表。这真的没关系,因为两者都被认为是好的“未定义”行为。

from CLHS关于这个的部分很短

如果字面量对象(包括引用 对象)被破坏性修改。

【讨论】:

  • 谢谢。可以肯定的是,如果我 copy-list 引用列表,则副本是可变的,对吗?
  • @Jean-ClaudeArbaut 是的,但只有自 copy-list 以来的顶层会进行浅拷贝。因此它不会像 copy-tree 那样递归地复制列表中的列表元素。
  • 还有一个问题:在函数nconcCLHS page中,有一个例子:(setq x '(abc)) (setq y '(def )) (nconc xy) 这不是错了吗?我的意思是,x 和 y 都持有一个 litteral 列表,并且 x 由 nconc 修改。
【解决方案2】:

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

【讨论】:

  • 这很有道理,谢谢。所以,可以肯定的是,如果(setf x '(nil)) (rplacd x 1) 做了我错误的预期,这要归功于未定义的后果?这里的 Scheme 更友好,因为它会通知错误 IIRW。
  • 当 SBCL 可以检测到常量数据的修改时,它会发出有用的警告并引用标准的适用位。 (这就是我查找回复的参考资料的方式。)但它还不能检测到所有情况。在那之前你只需要知道规则。
  • @Jean-ClaudeArbaut :一个旧的方案常见问题解答说:R5RS 的第 3.4 节指出,尝试修改不可变对象(例如文字列表)是错误的。并非所有的 Schemes 都会将此报告为错误,而是会修改文字列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-12
  • 1970-01-01
  • 2018-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多