我喜欢 Clojure。关于该语言让我困扰的一件事是我不知道惰性序列是如何实现的,或者它们是如何工作的。
我知道惰性序列只会评估序列中被要求的项目。它是如何做到这一点的?
- 是什么让惰性序列如此高效以至于它们不会消耗太多 堆栈?
- 你怎么能把递归调用包装成惰性序列而不 不再为大型计算获得堆栈溢出?
- 惰性序列会消耗哪些资源来完成它的工作?
- 惰性序列在哪些场景下效率低下?
- 惰性序列在哪些场景下最有效?
最佳答案
让我们开始吧。
• 我知道惰性序列只会评估序列中要求的项目,它是如何做到这一点的?
惰性序列(以下称为 LS,因为我是 LP,或 Lazy Person)由部分组成。 head,或部分(s,因为实际上一次评估 32 个元素,从 Clojure 1.1 开始,我认为是 1.2)已经评估过的序列,后面跟着一个叫做 thunk 的东西,它基本上是等待调用的一大块信息(将其视为创建序列的函数的其余部分,未计算)。当它被调用时,thunk 会评估对它的要求,并创建一个新的 thunk,并根据需要提供上下文(已经调用了多少,因此它可以从之前的位置恢复)。
因此您(take 10 (whole-numbers))
– 假设whole-numbers
是惰性整数序列。这意味着您将强制对 thunk 进行 10 次评估(尽管在内部这可能会因优化而略有不同。
• 是什么让惰性序列如此高效以至于它们不会消耗太多堆栈?
一旦您阅读了之前的答案(我希望),这一点就会变得更加清楚:除非您特别要求某些东西,否则不会评估任何东西。当您调用某些东西时,序列中的每个元素都可以单独求值,然后丢弃。
如果序列不是惰性的,它通常会保持在头部,这会消耗堆空间。如果它是惰性的,它会被计算,然后被丢弃,因为后续计算不需要它。
• 为什么可以将递归调用包装在一个惰性序列中,并且不再为大型计算获得堆栈溢出?
查看前面的答案并考虑:lazy-seq
宏 ( from the documentation ) 将
will invoke the body only the first time seq
is called, and will cache the result and return it on all subsequent
seq calls.
查看 filter
函数,了解使用递归的很酷的 LS:
(defn filter
"Returns a lazy sequence of the items in coll for which
(pred item) returns true. pred must be free of side-effects."
[pred coll]
(let [step (fn [p c]
(when-let [s (seq c)]
(if (p (first s))
(cons (first s) (filter p (rest s)))
(recur p (rest s)))))]
(lazy-seq (step pred coll))))
• 惰性序列会消耗哪些资源来完成它的工作?
我不太确定你在这里问什么。 LS 需要内存和 CPU 周期。他们只是不会不断地敲打堆栈,并用获取序列元素所需的计算结果填充它。
• 惰性序列在哪些场景下效率低下?
当您使用计算速度快且不会经常使用的小序列时,将其设为 LS 效率低下,因为它需要另外几个字符来创建。
说真的,除非你想做一些非常的东西,否则 LSs 是必经之路。
• 惰性序列在哪些场景下效率最高?
当您处理巨大的序列并且您只使用它们的零星片段时,这就是您从使用它们中获得最大 yield 的时候。
实际上,就便利性、易于理解(一旦您掌握了它们)和推理您的代码以及速度而言,使用 LS 优于非 LS 几乎总是更好。
关于clojure - Clojure 中的惰性序列是如何实现的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3247045/