clojure cons vs lazy-seq

cal*_*leb 24 stack-overflow clojure cons

为什么cons在这个上下文中使用lazy-seq,但是conj没有?

这有效:

(defn compound-interest [p i]
   (cons p (lazy-seq (compound-interest (* p (+ 1 i)) i))))
Run Code Online (Sandbox Code Playgroud)

这不会(它给出堆栈溢出[1]异常):

(defn compound-interest2 [p i]
   (conj (lazy-seq (compound-interest2 (* p (+ 1 i)) i)) p))
Run Code Online (Sandbox Code Playgroud)

[1]哦,你好!在stackoverflow上询问涉及堆栈溢出的问题.

Ale*_*x D 41

(conj collection item)补充itemcollection.要做到这一点,它需要实现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?(如何ConsLazySeq对象协同工作以形成懒惰的序列将在下面变得更加清晰.)看看第98行clojure/src/jvm/clojure/lang/LazySeq.java.注意它调用了一个名为的方法seq.这就是实现了价值LazySeq(跳转到第55行的细节).

所以你可以说conj需要知道你传递了什么样的集合,但cons事实并非如此.cons只要求"集合"参数是一个ISeq.

请注意,ConsClojure中的对象与其他Lisp中的"cons cells"不同 - 在大多数Lisp中,"cons"只是一个包含2个指向其他任意对象的对象.因此,您可以使用cons单元构建树,等等.Clojure Cons采用任意的Object头部和ISeq尾部.由于它们Cons自己实现ISeq,你可以用Cons对象构建序列,但它们也可以指向向量或列表等.(注意,Clojure中的"列表"是一个特殊的类型(PersistentList),并不是Cons对象构建的. )clojure.lang.LazySeq 实现ISeq,因此它可以用作尾部(Lisps中的"cdr")Cons.一个LazySeq拥有一些代码的参考评估ISeq某种形式的,但直到需要它实际上并没有评估的代码,并且它评估的代码后,将缓存返回ISeq,并委托给它.

......这一切都开始有意义吗?你知道懒惰的序列是如何工作的吗?基本上,你从一开始LazySeq.当LazySeq实现时,它评估为a Cons,指向另一个LazySeq.当那个实现了......你明白了.所以你得到一个LazySeq对象链,每个对象持有(并委托给)a Cons.

关于Clojure中"conses"和"lists"之间的区别,"lists"(PersistentList对象)包含一个缓存的"length"字段,因此它们可以count在O(1)时间内响应.这在其他Lisp中不起作用,因为在大多数Lisp中,"列表"是可变的.但是在Clojure中它们是不可变的,所以缓存的长度是有效的.

ConsClojure中的对象没有缓存长度 - 如果有,它们如何用于实现惰性(甚至无限)序列?如果你试图取counta Cons,它只调用count它的尾部,然后将结果递增1.

  • 很好的答案。除其他外,它还提供了另一个问题的答案:如果 Clojure 已经有了 PersistentList,但 Cons 中缺少长度字段,为什么还要有 Cons? (2认同)