"Cons" 在 Lisp 中如何工作?

How does "Cons" work in Lisp?

我正在学习 Lisp,但我没有 Lisp 编程经验。在我的部分学习中,我遇到了以下示例:

> (cons ‘a ‘(a b))  ----> (A A B)
> (cons ‘(a b) ‘a)  ----> ((A B).A)

我想知道为什么当我们有 (cons 'a '(a b)) 时响应是 (A A B) 以及为什么什么时候我们稍微改变一下,将 'a 放在 (a b) 之后,响应是一个虚线列表,如 ((A B ).A)?第一个代码行和第二个代码行有什么区别?这些代码背后发生了什么?

如果你把它们想象成cons-cells就很容易理解了。

简而言之,cons 单元格由正好 两个值组成。通常的表示法是使用点,例如:

(cons 'a 'b) ==> (A . B)

但是由于在 LISP 中经常使用列表,所以更好的表示法是去掉点。 列表是通过让第二个元素成为一个新的 cons 单元格,最后一个结束符(通常是 nil,或 Common Lisp 中的 '())来制作的。所以这两个是相等的:

(cons 'a (cons 'b '())) ==> (A B)
(list 'a 'b) ==> (A B)

因此 (cons 'a 'b) 创建一个单元格 [a,b](list 'a 'b) 将创建 [a, [b, nil]]。请注意在 cons 单元格中编码列表的约定:它们以内部 nil.

终止

现在,如果您将 'a 添加到最后一个列表中,您将创建一个包含 [[a, [b, nil]], a] 的新 cons 单元格。因为这不是 "proper" 列表,即它不是以 nil 结尾的,所以写出来的方法是使用点:(cons '(a b) 'a) ==> ((a b) . a).

如果没有打印点,则它必须是结构为 [[a, [b, nil]], [a, nil]].

的列表

你的例子

当您执行 (cons 'a '(a b)) 时,它会将符号 'a 和列表 '(a b) 放入新的 cons 单元格中。所以这将包括 [a, [a, [b, nil]]]。由于这自然以内nil结尾,所以它没有点写。

至于(cons '(a b) 'a),现在你会得到[[a, [b, nil]], a]。这 不会 以内部 nil 终止,因此将使用点表示法。

我们可以使用 cons 使最后一个示例以内部 nil 结尾吗?是的,如果我们这样做

(cons '(a b) (cons 'a '())) ==> ((A B) A)

最后,

(list '(a b) 'a))

等同于

(cons (cons (cons 'a (cons 'b '())) (cons 'a '())))

列表 (a b c) 表示(内部存储)为三个cons-cells:(cons 'a (cons 'b (cons 'c '())。请注意,最后一对的 cdr.

中有 '()

打印机将最后一个 cdr 为 '() 的一系列 cons-cells 打印为列表。因此,该示例打印为 (a b c).

让我们看看:(cons 'a '(a b))

列表'(a b)表示为(cons 'a (cons 'b '())。这意味着 (cons 'a '(a b)) 产生:(cons 'a (cons 'a (cons 'b '())).

我们来看看:(cons '(a b) 'a).

列表'(a b)表示为(cons 'a (cons 'b '())。这意味着 (cons (cons '(a b) 'a)) 产生 (cons (cons 'a (cons 'b '()) 'a).

请注意,本系列并未以 '() 结尾。显示打印机使用点符号。 ( ... . 'a)表示一个值由一系列cons-cells组成,最后一个cdr包含'a。因此,值 (cons (cons 'a (cons 'b '()) 'a) 打印为 '((a b) . a).

查看此可视化:

CL-USER 7 > (sdraw:sdraw '(A A B))

[*|*]--->[*|*]--->[*|*]--->NIL
 |        |        |
 v        v        v
 A        A        B

CL-USER 8 > (sdraw:sdraw '((A B) . A))

[*|*]--->A
 |
 v
[*|*]--->[*|*]--->NIL
 |        |
 v        v
 A        B

另外:

CL-USER 9 > (sdraw:sdraw '(A B))

[*|*]--->[*|*]--->NIL
 |        |
 v        v
 A        B

CL-USER 10 > (sdraw:sdraw (cons 'A '(A B)))

[*|*]--->[*|*]--->[*|*]--->NIL
 |        |        |
 v        v        v
 A        A        B

CL-USER 11 > (sdraw:sdraw (cons '(A B) 'A))

[*|*]--->A
 |
 v
[*|*]--->[*|*]--->NIL
 |        |
 v        v
 A        B

一个cons是一个可以包含两个值的数据结构。例如(cons 1 2) ; ==> (1 . 2)。第一部分是 car,第二部分是 cdr。如果 cdrnillist,则 conslist。因此 (1 . (2 . (3 . ()))) 是一个列表。

当打印 cons 时,当 cdrconsnil 时省略点。 cdr 的外括号也被省略。因此 (3 . ()) 打印为 (3)(1 . (2 . (3 . ()))) 打印为 (1 2 3)。它是相同的结构,但具有不同的可视化效果。 car中的一个cons没有这个规则。

cdr 是一个列表时,读取函数用点和奇怪的异常打印格式读取 cons。它在读取时的行为就像 cons.

对于 readprint 都有一个特殊的规则,即使它是 cons 的链,列表的错觉也是完整的。

(cons ‘a ‘(a b))  ----> (A . (A B)) 
(cons ‘(a b) ‘a)  ----> ((A B) . A)

打印时,第一个是一个包含 3 个元素的列表,因为 cdr 是一个列表。

cons 只是一对数据类型。例如,(cons 1 2) 是一对 12,打印时两个元素之间用点分隔,如 (1 . 2).

列表在内部表示为嵌套的conses,例如列表 (1 2 3)(cons 1 (cons 2 (cons 3 '()))