为什么需要使用以防止无限递归

时间:2016-03-12 01:52:57

标签: clojure lazy-evaluation

在定义无限序列时,我注意到有必要避免无限递归。但是,我不明白的是为什么。以下是有问题的代码:

kmalloc

这很有效;但由于我喜欢质疑事物,我开始想知道为什么需要利弊(除了包括0)。毕竟,lazy-seq函数创建了一个lazy-seq。这意味着,在调用(或分块)之前,不应计算其余值。所以,我试过了。

(defn even-numbers
  ([] (even-numbers 0))
  ([n] (cons n (lazy-seq (even-numbers (+ 2 n))))))

(take 10 (even-numbers))
;; (0 2 4 6 8 10 12 14 16 18)

所以,现在我知道缺点是必要的,但是我想知道为什么有必要对导致懒惰的序列进行懒惰的评估

1 个答案:

答案 0 :(得分:8)

延迟seqs是一种推迟实际seq元素计算的方法,但最终需要计算这些元素。这实际上不必涉及cons - 例如clojure.core/concat在处理分块操作数时使用“chunked conses”,并且可以在lazy-seq中包装任何具体的seq类型 - 但是有些然而,如果要进行任何seq处理,则需要多层lazy-seq之后的非惰性返回。否则甚至不会有第一个要素。

让自己处于一个懒惰的seq函数的位置。实际上,调用者已经告诉它“这里的所有意图和目的都是seq,但我不想在以后计算实际元素”。现在我们的函数需要一些实际的元素来操作,所以它戳戳并刺激seq试图让它产生一些元素......然后是什么?

如果剥离一些lazy-seq层最终会产生一个Cons单元格,一个列表,一个矢量上的seq或任何其他具有实际元素的具体seq类型的东西,那么很棒,该函数可以读取从中获取元素并取得进展。

但是,如果剥离这些图层的唯一结果是显示更多图层,并且lazy-seq一直向下,那么......没有找到任何元素。而且由于原则上没有办法确定是否通过剥离足够多的层最终会产生一些元素(参见the halting problem),消耗这种不可实现的懒惰seq的函数通常别无选择,只能继续循环。

要采取另一个角度,让我们考虑一下您的even-numbers-v2功能。它接受一个参数并返回一个lazy-seq对象,包含对自身的进一步调用。现在,它接收的原始参数(n)用于计算递归调用的参数((+ 2 n)),但是否则不会放在任何数据结构中或以其他方式传递给调用者,所以没有理由将它作为生成的seq的元素出现。所有调用者都看到该函数已经生成了一个懒惰的seq对象,它只能在搜索序列的实际元素时展开它;当然,情况会重演(在这种情况下并非严格地说,但这只是因为+最终会在处理多头时抱怨算术溢出)。