让我们这样做吧。
• 我知道惰性序列只评估序列中被请求的项目,它是如何做到的?
懒惰的序列(以下称为 LS,因为我是 LP,或懒惰的人)由部分组成。 头部或部分(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 效率低下,因为它需要创建另外几个字符。
说真的,除非你试图让某些东西非常表现出色,否则 LS 是要走的路。
• 在什么情况下惰性序列最有效?
当您处理巨大的 seq 并且您只使用它们的零碎部分时,那就是您从使用它们中获得最大收益的时候。
真的,就方便性、易于理解(一旦掌握它们)、对代码的推理以及速度而言,使用 LS 总比使用非 LS 好得多。