clojure cons vs lazy-seq

时间:2012-09-12 13:28:53

标签: clojure stack-overflow cons

为什么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上询问涉及堆栈溢出的问题。

1 个答案:

答案 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方法调用时会发生什么? (ConsLazySeq对象如何协同工作以形成延迟序列将在下面变得更加清晰。)请看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中,它们是不可变的,所以缓存它的长度是可行的。

Clojure 中的

count对象没有缓存长度 - 如果有,它们如何用于实现惰性(甚至无限)序列?如果您尝试使用Cons的{​​{1}},则只需在其尾部调用count,然后将结果递增1。