clojure - 了解 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 引入了“分块”序列,
这可以提供更高的效率......作为普通 seqs 的 chunked-seqs 的消耗应该是完全透明的。但是,请注意,某些序列处理一次最多会发生 32 个元素。如果您依靠完全的懒惰来排除任何非消耗结果的生成,这可能对您很重要。[ “1.1 版中对 Clojure 的更改”第 2.3 节]
在您的示例(range)
中,似乎正在生成一个序列,该序列一次实现一个元素并(range 999)
正在生成一个分块序列。map
将一次消耗一个分块序列,产生一个分块序列。因此,当 take 请求分块 seq 的第一个元素时,传递给 map 的函数在值 0 到 31 上被调用 32 次。
我相信以这种方式编码是最明智的,如果该函数产生具有任意大块的分块序列,则该代码仍然适用于任何产生序列的函数/参数。
我不知道是否有人编写了一个不分块的 seq 生成函数,是否可以依赖当前和未来版本的库函数(如 map 和 filter)不将 seq 转换为分块 seq。
但是,为什么会有差异?产生的 seq 有哪些实现细节(range)
和不同之处?(range 999)
- Range 在clojure.core中实现。
(range)
定义为(iterate inc' 0)
。- 最终 iterate 的功能由 Iterate.java 中的Iterate类提供。
(range end)
定义为,当 end 为 long 时,为(clojure.lang.LongRange/create end)
- LongRange 类位于LongRange.java中。
查看这两个 java 文件可以看出 LongRange 类实现IChunkedSeq
了 Iterator 类没有。(练习留给读者。)
猜测
- clojure.lang.Iterator 的实现不分块,因为迭代器可以被赋予任意复杂度的函数,并且分块的效率很容易被计算出比需要更多的值所淹没。
- 的实现
(range)
依赖于迭代器,而不是执行分块的自定义优化 Java 类,因为这种(range)
情况被认为不够普遍,无法保证优化。
推荐阅读
- django - 尝试访问 api 时,Django_hosts 集成会产生“NoReverseMatch at /en/api/”
- python - 当脚本在多处理工作人员中运行异步事件循环时,通过子进程运行脚本会挂起
- javascript - .findOne 函数,试图有多个参数
- python - 无法导入 Python 模块
- postgresql - 如何在 Windows 上使用 pg_squeeze
- php - PHP如何在数组中设置手动键值而不是多维数组中的索引?
- scala - 如何找到2个变量中存在的2个lat lng之间的距离哪个2个变量的数组
- java - 我有这个错误:java.sql.SQLException:Java 应用程序中的列名无效
- php - 我想通过一个信号向特定用户发送通知
- r - 在 intsvy 库中使用 pirls.select.merge() 导入数据时出错