问题描述
简单代码:
> (cons null (cons 1 2))
'(() 1 . 2)
> (cons (cons 1 2) null)
'((1 . 2))
最初,我希望结果是相同的。我可以想到一些模糊的解释,但也想听听博学的人的观点。
为什么结果不同?
解决方法
按照数学的说法,某些运算是可交换的。加法是可交换的,因此(+ 1 2)
和(+ 2 1)
的结果相同。除法不是可交换的; (/ 1 2)
和(/ 2 1)
的结果不同。
OP对(cons null (cons 1 2))
和(cons null (cons 1 2))
应该具有相同结果的期望实际上是对cons
是可交换过程的期望。它不是。如果cons
是可交换的,则(cons 1 2)
和(cons 2 1)
是等效的。但是:
> (cons 1 2)
(1 . 2)
> (cons 2 1)
(2 . 1)
cons
过程创建一个cons
单元,该单元具有两个传统上称为car
和cdr
的组件;它根据两个参数创建cons
单元格。第一个参数放在单元格的car
中,第二个参数放在单元格的cdr
中。对于(cons 1 2)
,将1放置在car
中,将2放置在cdr
中;但对于(cons 2 1)
,2放置在car
中,而1放置在cdr
中。您可以看到(cons 1 2)
和(cons 2 1)
必须有所不同,因为car
单元格的cdr
和cons
是不同的。
OP示例(cons null (cons 1 2))
将空白列表放置在car
单元格的cons
中,并放置单元格(1 . 2)
(它是由另一个对{{1}的调用创建的) })在该单元格的cons
中。这是点缀列表(如下所述)cdr
;这样的虚线列表通常在REPL中表示为(() . (1 . 2))
。
但是使用(() 1 . 2)
时,将单元格(cons (cons 1 2) null)
放置在(1 . 2)
单元格的car
中,并将空列表放在该位置的cons
中单元格:cdr
。
现在,在Lisps中,列表是由((1 . 2) . ())
个单元格组成的链,这些单元格以空列表结尾。像cons
这样的链有时称为点缀列表或不正确列表;这是一个(1 . (2 . 3))
单元格链,该单元格不以空列表结尾。在REPL中,您经常会看到cons
表示为(1 . (2 . 3))
。另一方面,像(1 2 . 3)
之类的链则称为适当列表;这是一个(1 . (2 . ()))
单元格的链,它们确实以空列表终止。您会在REPL中将其表示为cons
。通常,当某人使用不合格的术语 list 时,它们的意思是正确的列表。
因此,OP代码(1 2)
等同于包含单个成员((1 . 2) . ())
的适当列表((1 . 2))
,并且肯定与虚线列表(1 . 2)
不同。
还有另一个过程(() 1 . 2)
,其作用与OP预期的一样。 append
和(append null (append '(1) '(2)))
均得出(append (append '(1) '(2)) null)
。最后两个表达式的计算结果相同,因为空列表是操作'(1 2)
的 identity元素。也就是说,将非空项目列表与空列表结合起来会得到非空列表的副本。类似地,将一个空列表与一个非空列表连接在一起会返回该非空列表的副本。但是append
不是可交换的; append
返回一个列表,其中包含第一个列表的元素,后跟第二个列表的元素。因此,append
-> (append '(1 2) '(3 4))
,但是'(1 2 3 4)
-> (append '(3 4) '(1 2))
。
还有另一个相关的数学属性:关联性。加法是关联的,即'(3 4 1 2)
和(+ 1 (+ 2 3))
的计算结果相同。使用(+ (+ 1 2) 3)
和1 + (2 + 3)
的中缀表示法都得出相同的结果。这有一个有趣的结果。由于分组并不重要,因此可以简单地将上面的中缀表达式写为(1 + 2) + 3
。相应的前缀表达式为1 + 2 + 3
,Lisps确实支持这种为多个操作数表达加法的方法。
(+ 1 2 3)
是非关联的。由于cons
根据其参数创建一个对,因此它不能是关联的。使用cons
对(cons 1 2)
被创建。使用(1 . 2)
,将对(cons 1 (cons 2 3))
放在另一对的(2 . 3)
中,结果是cdr
(在REPL中可能表示为(1 . (2 . 3))
)。但是,使用(1 2 . 3)
对(cons (cons 1 2) 3)
被创建并放置在另一对(1 . 2)
中,从而产生car
。请注意,由于((1 . 2) . 3)
不具有关联性,因此cons
之类的表达式没有意义;操作分组不清楚。
但是(cons 1 2 3)
是关联的。使用append
创建列表(append '(1) '(2))
。使用(1 2)
创建列表(append '(1) (append '(2) '(3)))
,然后将列表(2 3)
与(1)
合并,得到(2 3)
。使用(1 2 3)
可以创建列表(append (append '(1) '(2)) '(3))
,然后将其与列表(1 2)
结合在一起,也将得到列表(3)
。由于分组在这里并不重要,因此简单地编写(1 2 3)
是有意义的,此外,Lisps确实支持这种表示附加操作的方式。
请参见SICP here中的“框和指针”图。 cons
是成对的构造器-它只是将两件事放在一起。如果我们概括这种配对事物的概念,我们可以建造树。在方案/机架中,列表只是树的一种特殊情况(每个“左”分支保存列表的一个元素,每个右分支保存列表的其余部分)。
速记:由于编写(cons 1 (cons 2 (cons 3 null)))
很麻烦,因此我们简化了:'(1 2 3)
。注意,最后的null
(或'()
)在缩写中被省略了。如果它不是null而是4
,我们将得到以下简写:'(1 2 3 . 4)
。点表示该结构的右侧不是列表。
对于第一个示例,null
只是列表的元素-出现在左分支上。对于第二个示例,null
是最右边的元素-表示列表的末尾。
/ \ Null is displayed as () in
/ \ the shorthand version. Whenever
null a pair does not have a list
/ \ on the right,we see a dot.
/ \
1 2
/ \ Note here that the null
/ \ is on the last right-most
null branch. This null is not
/ \ shown in the shorthand.
/ \
1 2