clojure - Clojure 中的惰性序列是如何实现的?

标签 clojure lisp lazy-evaluation lazy-sequences

我喜欢 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/

相关文章:

lisp - 从 s 表达式创建 lambda

clojure - 在 Clojure 中实现整数?计划中

recursion - 用于计算组合的尾递归 Clojure 函数

lisp - 如何检查所有列表是否都是数字

swift - 在惰性存储属性中使用函数时出现编译器错误

javascript - 等效于 JavaScript 中的 Scala View

java - Java 流是否能够从 map /过滤器条件中懒惰地减少?

clojure - 启动 : serve non-root directory from classpath in handler + cljs reload

clojure - Clojure 中的数据格式安全

口齿不清错误 : should be lambda expression