我当时在学习Lisp,对Lisp编程没有经验。在我的部分研究中,我遇到了以下示例:
> (cons ‘a ‘(a b)) ----> (A A B)
> (cons ‘(a b) ‘a) ----> ((A B).A)
我想知道为什么当我们有(缺点'a '(a b))时,响应是(A A B)以及为什么当我们稍微改变它并将'a放在(a b)之后时,响应是一个虚线列表,如((A B)。A)?第一行代码和第二行代码有什么区别?这些代码背后是怎么回事?
如果您将它们视为缺点单元格,则很容易理解。
简而言之,缺点单元格正好由两个值组成。通常的表示法是使用点,例如:
(cons 'a 'b) ==> (A . B)
但是由于列表在 LISP 中经常使用,更好的表示法是去掉点。 列表是通过让第二个元素成为一个新的 cons 单元格来创建的,最后一个元素以终止符结尾(在 Common Lisp 中通常为 nil 或'()
)。所以这两者是相等的:
(cons 'a (cons 'b '())) ==> (A B)
(list 'a 'b) ==> (A B)
所以(cons 'a 'b)
会创建一个单元格[a,b]
,(list 'a 'b)
会创建一个[a, [b, nil]]
。 请注意在 cons 单元格中编码列表的约定:它们以内部nil
终止。
现在,如果您将 cons'a
到最后一个列表中,则创建一个包含[[a, [b, nil]], a]
的新 cons 单元格。由于这不是一个"正确"的列表,即它不以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
终止,因此将使用点表示法。
我们可以使用缺点使最后一个示例以内在 nil 结尾吗?是的,如果我们这样做
(cons '(a b) (cons 'a '())) ==> ((A B) A)
最后,
(list '(a b) 'a))
相当于
(cons (cons (cons 'a (cons 'b '())) (cons '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
。如果cons
cdr
是nil
或list
,则list
。因此(1 . (2 . (3 . ())))
是一个列表。
打印cons
时,当cdr
为cons
或nil
时,将省略点。cdr
的外括号也被省略。 因此,(3 . ())
被打印(3)
,(1 . (2 . (3 . ())))
被打印(1 2 3)
。 它是相同的结构,但具有不同的可视化效果。car
中的cons
没有此规则。
当cdr
为列表时,读取功能用点和奇怪的特殊打印格式读取cons
。它将在读取时表现得好像它是cons
.
有了针对read
和print
的特殊规则,即使列表是cons
链,列表的错觉也是完整的。
(cons ‘a ‘(a b)) ----> (A . (A B))
(cons ‘(a b) ‘a) ----> ((A B) . A)
打印时,第一个是包含 3 个元素的列表,因为cdr
是一个列表。
列表 (a b c) 表示(存储在内部)为三个 cons-cell:(cons 'a (cons 'b (cons 'c '())
。请注意,最后一对的 cdr 中有 '()。
打印机将最后一个 cdr 为 '() 的一系列 cons-cell 打印为列表。因此,该示例打印为 (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-cell 组成,最后一个 cdr 包含'a
。因此,(cons (cons 'a (cons 'b '()) 'a)
值打印为'((a b) . a)
。
缺点只是一个对数据类型。例如,(cons 1 2)
是一对1
和2
,它将用一个点状(1 . 2)
分隔的两个元素进行打印。
列表在内部表示为嵌套的 conses,例如,列表(1 2 3)
是(cons 1 (cons 2 (cons 3 '()))
的。
=> 在终端中使用 sbcl
$ rlwrap sbcl
-
(ql:quickload "draw-cons-tree")
要加载"绘制缺点树": 加载 1 个 ASDF 系统: 绘制缺点树 ;加载"绘制树">
("绘制缺点树")
(画树- :画树'(1 2 3 4))