为什么(类型列表)等于CONS?

时间:2016-11-12 08:25:45

标签: lisp common-lisp

我正在玩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))是有利的,即使它显然是一个列表(否则它将是我理解的不合适的列表)。

2 个答案:

答案 0 :(得分:7)

在类型级别未定义正确和不正确的列表。这将需要递归类型定义,这只能使用satisfies类型的Lisp,并且在这种情况下type-of仍然不会返回复杂的类型说明符:

  

湾返回的类型不涉及andeql,   membernotorsatisfiesvalues

list类型可以定义为(or cons null)

  

类型consnull构成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)

@ coredump的回答是正确的,但看到实际的理由为什么它是正确的可能是有用的。

首先, 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 tlist最初是一个正确的列表,然后它不是因为我摆弄了它的最终缺点,那么它又是因为我摆弄有一些中间缺点(现在,无论我对last的限制所做的事情都与list的约束无关。

如果你想使用像链接列表这样的任何东西,那么就代表类型来说,跟踪某些东西是否是正确的列表是非常困难的。例如,type-of会告诉您某些内容的代表类型,对于空列表而言只能是cons(或null。)