@coredump 的答案是正确的,但了解实际原因可能很有用为什么它是正确的。
首先,类型检查很快是非常可取的。所以如果我说(typep x 'list),我希望它不必离开很长时间来进行检查。
好吧,考虑一下合适的列表检查器应该是什么样子。可能是这样的:
(defun proper-list-p (x)
(typecase x
(null t)
(cons (proper-list-p (rest x)))
(t nil)))
对于任何好的 CL 编译器,这都是一个循环(如果您可能需要处理初级编译器,它显然可以重写为显式循环)。但它是一个循环,与您正在检查的列表一样长,这会导致“类型检查应该快速”测试失败。
事实上,它没有通过更严重的测试:类型检查应该终止。考虑像@987654323@ 这样的电话。哎呀。所以我们需要这样的东西,也许:
(defun proper-list-p (x)
(labels ((plp (thing seen)
(typecase thing
(null (values t nil))
(cons
(if (member thing seen)
(values nil t) ;or t t?
(plp (rest thing)
(cons thing seen))))
(t (values nil nil)))))
(plp x '())))
好吧,这将终止(并告诉你列表是否是循环的):
> (proper-list-p '#1=(1 . #1#))
nil
t
(这个版本认为循环列表不合适:我认为另一个决定不太有用,但在某些理论上可能同样合理。)
但这现在是列表长度中的二次。这可以通过以显而易见的方式使用哈希表来做得更好,但是对于小列表(哈希表很大),实现是可笑的。
另一个原因是考虑representational类型和intentional类型之间的区别:某事物的表示类型告诉你它是如何实现的,而意图类型告诉你什么它在逻辑上是。很容易看出,在具有可变数据结构的 lisp 中,(非空)列表的表示类型与 cons 的表示类型不同是非常困难的。下面是一个例子:
(defun make-list/last (length init)
;; return a list of length LENGTH, with each element being INIT,
;; and its last cons.
(labels ((mlt (n list last)
(cond ((zerop n)
(values list last))
((null last)
(let ((c (cons init nil)))
(mlt (- n 1) c c)))
(t (mlt (- n 1) (cons init list) last)))))
(mlt length '() '())))
(multiple-value-bind (list last) (make-list/last 10 3)
(values
(proper-list-p list)
(progn
(setf (cdr last) t)
(proper-list-p list))
(progn
(setf (cdr (cdr list)) '(2 3))
(proper-list-p list))))
所以最后一种形式的结果是t nil t:list 最初是一个正确的列表,然后不是因为我摆弄了它的最终缺点,然后又是因为我摆弄了一些中间缺点(和现在,无论我对绑定到 last 的 cons 做什么,都不会影响绑定到 list 的 cons。
如果您想使用任何类似于链表的东西,那么就表示类型而言,要跟踪某物是否是正确的列表将非常困难。例如,type-of 会告诉您某事物的表示类型,它只能是 cons(或 null 用于空列表)。