为什么cons在这个上下文中与lazy-seq一起工作,但是没有&#t;
这有效:
(defn compound-interest [p i]
(cons p (lazy-seq (compound-interest (* p (+ 1 i)) i))))
这不是(它给出了堆栈溢出[1]异常):
(defn compound-interest2 [p i]
(conj (lazy-seq (compound-interest2 (* p (+ 1 i)) i)) p))
[1]哦,你好!在stackoverflow上询问涉及堆栈溢出的问题。
答案 0 :(得分:41)
(conj collection item)
将item
添加到collection
。为此,它需要实现collection
。 (我将在下面解释原因。)因此,递归调用会立即发生,而不是被推迟。
(cons item collection)
创建一个以item
开头的序列,然后是collection
中的所有内容。值得注意的是,不需要实现collection
。因此,递归调用将被推迟(因为使用lazy-seq
),直到有人试图得到结果序列的尾部。
我将在内部解释这是如何工作的:
cons
实际上返回一个clojure.lang.Cons
对象,这是懒惰序列的组成部分。 conj
返回您传递的相同类型的集合(无论是列表,向量还是其他任何集合)。 conj
使用对集合本身的多态Java方法调用来完成此操作。 (见line 524 of clojure/src/jvm/clojure/lang/RT.java
。)
在clojure.lang.LazySeq
返回的lazy-seq
对象上发生Java方法调用时会发生什么? (Cons
和LazySeq
对象如何协同工作以形成延迟序列将在下面变得更加清晰。)请看line 98 of clojure/src/jvm/clojure/lang/LazySeq.java
。请注意,它调用了一个名为seq
的方法。这就是实现LazySeq
的价值(跳转到line 55以获取详细信息)。
所以你可以说conj
需要确切地知道你传递了什么类型的集合,但cons
没有。 cons
只需要“collection”参数为ISeq
。
请注意,Clojure中的Cons
对象与其他Lisp中的“cons cells”不同 - 在大多数Lisp中,“cons”只是一个包含2个指向其他任意对象的对象。因此,您可以使用cons单元构建树,等等。 Clojure Cons
以任意Object
为头,ISeq
为尾。由于Cons
本身实现了ISeq
,因此您可以使用Cons
个对象构建序列,但它们也可以指向向量或列表等。(请注意,“列表”在Clojure是一种特殊类型(PersistentList
),并且不是从Cons
个对象构建。)clojure.lang.LazySeq
也实现{{1因此它可以用作ISeq
的尾部(Lisp中的“cdr”)。 Cons
包含对某些评估到某种LazySeq
的代码的引用,但它实际上并不会在需要之前评估该代码,并且在评估之后代码,它缓存返回的ISeq
并委托给它。
......这一切都开始有意义吗?你了解懒惰序列是如何工作的吗?基本上,您从ISeq
开始。实现LazySeq
后,评估为LazySeq
,指向另一个Cons
。当那个实现了......你明白了。因此,您获得了一系列LazySeq
个对象,每个对象都持有(并委托给)LazySeq
。
关于Clojure中“conses”和“lists”之间的区别,“lists”(Cons
个对象)包含一个缓存的“length”字段,因此它们可以响应O中的PersistentList
(1 ) 时间。这在其他Lisp中不起作用,因为在大多数Lisp中,“列表”是可变的。但是在Clojure中,它们是不可变的,所以缓存它的长度是可行的。
count
对象
Cons
的{{1}},则只需在其尾部调用count
,然后将结果递增1。