构建带有非列表尾部的列表有什么意义?

What's the point of building a list with a non-list tail?

Emacs lisp manual 关于函数 nconc 的说明:

Since the last argument of nconc is not itself modified, it is reasonable to use a constant list, such as '(4 5), as in the above example. For the same reason, the last argument need not be a list

我确实会写

(setq x '(1 2 3))
=> (1 2 3)

(nconc x 0)
=> (1 2 3 . 0)

但这会产生一个完全损坏的列表:

(length x)
=> eval: Wrong type argument: listp, 0

(butlast x)
=> butlast: Wrong type argument: listp, 0

它们不是 "broken" 列表;它们实际上被称为 不正确的 列表(与 nil 终止的列表相反,后者是 正确的 列表)。许多列表函数,例如您刚刚命名的 lengthbutlast,需要正确的列表,而 listp returns 仅适用于正确的列表。

关联列表中使用了不正确的列表(其中关联通常不正确;尽管列表本身必须正确)。


如果你想把一个不正确的列表变成正确的,你有两个选择:

  • 删除 "improper" 元素。
  • 将不正确的元素视为正确列表的最后一个元素。

这是我编写的一个名为 properise 的过程,它将执行前者:

(defun properise (x)
  (let ((r nil))
    (while (consp x)
      (push (pop x) r))
    (nreverse r)))

(如果您想要后一种行为,请在 nreverse 行之前添加 (unless (null x) (push x r))。)

通常我会避免创建这样的数据结构,其中最后一个元素是一个 cons 单元格,其中包含 cdr 中 NIL 以外的某些对象...这会使调试变得更加困难,这是一种 hack,使代码更难以理解, ...

我仍然不确定为什么这是一个好的模式,但这里有一种简单的方法可以从不正确的列表中获取正确的列表,而无需复制:

(defun nmake-proper-list (x)
  (let ((y (last x)))
    (setcdr y nil)
    x))