新手问题理解 clojure 懒惰序列



我刚刚开始学习Clojure,我对懒惰序列的工作原理感到困惑。特别是,我不明白为什么这两个表达式在 repl 中产生不同的结果:

;; infinite range works OK
(user=> (take 3 (map #(/(- % 5)) (range)))
(-1/5 -1/4 -1/3)
;; finite range causes error
user=> (take 3 (map #(/(- % 5)) (range 1000)))
Error printing return value (ArithmeticException) at clojure.lang.Numbers/divide (Numbers.java:188).
Divide by zero

我取整数序列(0 1 2 3 ...)并应用一个减去 5 然后取倒数的函数。显然,如果将其应用于 5,则会导致除以零错误。但是由于我只从惰性序列中获取前 3 个值,因此我没想到会看到异常。

当我使用所有整数时,结果是我所期望的,但是如果我使用前 1000 个整数,我会得到错误。

为什么结果不同?

Clojure 1.1引入了"分块"序列,

这可以提供更高的效率...分块序列的消耗为 正常的序列应该是完全透明的。但是,请注意,一些 序列处理一次最多可以进行 32 个元素。这可以 如果你依靠完全懒惰来排除 生成任何未消耗的结果。["1.1版对Clojure的更改"第2.3节]

在您的示例中(range)似乎正在生成一个一次实现一个元素的 seq,(range 999)正在生成一个分块的 seq。map将一次消耗一个分块的序列,从而生成一个分块的序列。因此,当 take 询问分块 seq 的第一个元素时,传递给 map 的函数在值 0 到 31 上被调用 32 次。

我相信以这样的方式编码是最明智的,如果该函数产生具有任意大块的分块 seq,那么代码仍然适用于任何产生 seq 的函数/arity。

我不知道一个人是否编写了一个不分块的 seq 生成函数,如果一个人可以依靠当前和未来版本的库函数(如 map 和过滤器)不将 seq 转换为分块的 seq。

但是,为什么会有差异?实现细节是什么,以至于(range)(range 999)在生成的序列类型上有所不同?

  1. 范围在 clojure.core 中实现。
  2. (range)定义为(iterate inc' 0)
  3. 最终,Iterate的功能由Iterate.java中的Iterate类提供。
  4. 当结束为长时,(range end)定义为(clojure.lang.LongRange/create end)
  5. 远程
  6. 类住在远程.java。

查看这两个 java 文件可以看出,LongRange 类实现了IChunkedSeq,而 Iterator 类没有。 (练习留给读者。

投机

  1. clojure.lang.Iterator 的实现不会分块,因为迭代器可以被赋予任意复杂度的函数,并且分块的效率很容易被计算比需要的更多的值所淹没。
  2. (range)的实现依赖于迭代器,而不是执行分块的自定义优化 Java 类,因为(range)情况被认为不够常见,无法保证优化。

相关内容

  • 没有找到相关文章

最新更新