(conj collection item)
添加item
到collection
. 为此,它需要实现collection
. (我将在下面解释原因。)所以递归调用立即发生,而不是被推迟。
(cons item collection)
创建一个以 开头的序列item
,然后是collection
. 值得注意的是,它不需要实现collection
。所以递归调用将被推迟(因为使用lazy-seq
),直到有人试图得到结果序列的尾部。
我将解释这是如何在内部工作的:
cons
实际上返回一个clojure.lang.Cons
对象,这就是惰性序列的组成部分。conj
返回您传递给它的相同类型的集合(无论是列表、向量还是其他)。conj
使用对集合本身的多态 Java 方法调用来执行此操作。(见第 524 行clojure/src/jvm/clojure/lang/RT.java
。)
当 Java 方法调用发生在由clojure.lang.LazySeq
返回的对象上时会发生什么lazy-seq
?(如何Cons
和LazySeq
对象一起工作以形成惰性序列将在下面变得更清楚。)查看 .的第 98 行clojure/src/jvm/clojure/lang/LazySeq.java
。请注意,它调用了一个名为seq
. 这就是实现的价值LazySeq
(详情请跳至第 55 行)。
所以你可以说它conj
需要确切地知道你通过了什么样的集合,但cons
没有。cons
只要求“集合”参数是一个ISeq
.
请注意,Cons
Clojure 中的对象与其他 Lisps 中的“cons cells”不同——在大多数 Lisps 中,“cons”只是一个对象,它包含两个指向其他任意对象的指针。所以你可以使用 cons 单元来构建树,等等。ClojureCons
将任意一个Object
作为头部,将一个ISeq
作为尾部。由于Cons
它本身实现ISeq
了 ,因此您可以从对象构建序列Cons
,但它们也可以指向向量或列表等。(请注意,Clojure 中的“列表”是一种特殊类型 ( PersistentList
),而不是Cons
从对象构建的。 )clojure.lang.LazySeq
也实现ISeq
了 ,因此它可以用作 a 的尾部(Lisps 中的“cdr”)Cons
。LazySeq
到ISeq
某种类型的,但在需要之前它实际上不会评估该代码,并且在它评估代码之后,它会缓存返回ISeq
并委托给它。
...这一切都开始有意义了吗?你知道惰性序列是如何工作的吗?基本上,您从LazySeq
. 当LazySeq
实现时,它的计算结果为 a Cons
,它指向另一个LazySeq
。当那个被实现时……你明白了。所以你得到一个LazySeq
对象链,每个对象都持有(并委托给) a Cons
。
关于 Clojure 中“conses”和“lists”的区别,“lists”(对象)包含一个缓存的“length”字段,因此它们可以在 O(1) 时间内PersistentList
响应。count
这在其他 Lisps 中不起作用,因为在大多数 Lisps 中,“列表”是可变的。但是在 Clojure 中它们是不可变的,所以缓存长度是可行的。
Cons
Clojure 中的对象没有缓存长度——如果有,它们如何用于实现惰性(甚至无限)序列?如果您尝试使用count
a Cons
,它只会调用count
它的尾部,然后将结果加 1。