我正在玩Common Lisp并且意识到了
(type-of (cons 1 2)) is CONS
和
(type-of (list 1 2)) is also CONS
然而,两者显然不一样,因为所有“正确”的列表必须与第二个元素列表一致。
也就是说,当只有两个元素时,第二个元素是2,第一个元素是1,也不是列表,但构造仍然被称为缺点。
自
以来,这更令人困惑(print (list (cons 1 2) 3)) ; this is a ((1 . 2) 3), an improper list, but still cons
(print (cons 1 (list 2 3))) ; this is a (1 2 3), a proper list, but still cons
(cons 1 (cons 2 3)) ; is not a proper list, but is a (1 2 . 3), but still cons...
一切都很好,但为什么(list 1 2)
不是列表?它不能成为一个缺点,因为cons和list必须是不同的类型,以便在算法中被分开来确定它是否是一个正确的列表(反过来,(equal (list 1 2) (cons 1 2))
应该是真的;没有这个歧视,利弊和清单之间应该没有区别,只会有缺点。
有人可以帮助我理解为什么它说(type-of (list 1 2))
是有利的,即使它显然是一个列表(否则它将是我理解的不合适的列表)。
答案 0 :(得分:7)
在类型级别未定义正确和不正确的列表。这将需要递归类型定义,这只能使用satisfies
类型的Lisp,并且在这种情况下type-of
仍然不会返回复杂的类型说明符:
湾返回的类型不涉及
and
,eql
,member
,not
,or
,satisfies
或values
。
list
类型可以定义为(or cons null)
:
类型
cons
和null
构成list
类型的详尽分区。
这意味着nil
是一个列表,任何cons
单元格都是一个列表。另见listp
的定义。
换句话说:
(typep '(a b c) 'cons)
=> T
但是:
(typep '(a b c) 'list)
=> T
当然,对于任何超类型都是如此:
(typep '(a b c) 'sequence)
=> T
(typep '(a b c) 't)
=> T
type-of
函数返回最基本的类型,即cons
,可以将其视为没有其他子类型满足typep
的类型(但请阅读specification这给出了实际的定义。)
只是为了澄清:
(cons 1 2)
... 是一个列表,但它不能传递给期望正确列表如map
等的函数。这在运行时检查,通常没有混淆,因为使用不正确列表的情况实际上非常罕见(例如,当您将cons
单元格视为树时)。同样,循环列表需要特殊处理。
为了检查列表是否合适,您只需要检查上一个cons
是否nil
作为其cdr
。
另外,我看到你写道:
((1 . 2) 3) ; [...] an improper list
你在这里有两个元素的正确列表,其中第一个是不正确的列表,a.k.a。是一个虚线列表。
答案 1 :(得分:3)
首先, typechecks很快非常可取。因此,如果我说(typep x 'list)
,我希望不要长时间离开去检查。
好吧,考虑一下正确的列表检查器必须是什么样子。这样的事情,也许是:
(defun proper-list-p (x)
(typecase x
(null t)
(cons (proper-list-p (rest x)))
(t nil)))
对于任何好的CL编译器,这是一个循环(如果你可能需要处理基本的编译器,它显然可以重写为显式循环)。但它是一个循环,只要你正在检查的列表,这就失败了' typechecks应该快速'测试
实际上它没有通过更严格的测试: typechecks应该终止。考虑像(proper-list-p #1=(1 . #1#))
这样的电话。哎呀。所以我们需要这样的东西,或许:
(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
(这个版本认为循环列表不合适:我认为另一个决定不太有用,但在某些理论意义上可能同样合理。)
但现在这是列表长度中的二次。通过以明显的方式使用哈希表可以使这更好,但是对于小列表(哈希表很大),实现是非常简单的。
另一个原因是要考虑代表性类型和有意类型之间的区别:某事物的表征类型告诉你它是如何实现的,而有意类型告诉你什么它在逻辑上是。并且很容易看出,在具有可变数据结构的口齿不清的情况下,(非空)列表的表示类型与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
的限制所做的事情都与list
的约束无关。
如果你想使用像链接列表这样的任何东西,那么就代表类型来说,跟踪某些东西是否是正确的列表是非常困难的。例如,type-of
会告诉您某些内容的代表类型,对于空列表而言只能是cons
(或null
。)