gpt4 book ai didi

clojure - 理解 Clojure 惰性序列的新手问题

转载 作者:行者123 更新时间:2023-12-04 07:23:51 28 4
gpt4 key购买 nike

我刚刚开始学习 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 引入了“分块”序列,

This can provide greater efficiency ... Consumption of chunked-seqs asnormal seqs should be completely transparent. However, note that somesequence processing will occur up to 32 elements at a time. This couldmatter to you if you are relying on full laziness to preclude thegeneration of any non-consumed results. [Section 2.3 of "Changes to Clojure in Version 1.1"]


在您的示例中 (range)似乎正在生成一个一次实现一个元素的 seq 和 (range 999)正在产生一个分块的序列。 map将一次消耗一个分块的 seq 一个块,产生一个分块的 seq。因此,当 take 请求分块序列的第一个元素时,传递给 map 的函数在值 0 到 31 上被调用 32 次。
我相信以这样的方式编码是最明智的,如果该函数产生具有任意大块的分块 seq,则代码仍然适用于任何 seq 生成函数/arity。
如果可以依靠当前和 future 版本的库函数(例如 map 和 filter)不将 seq 转换为分块的 seq,我不知道是否有人编写了一个不分块的 seq 生成函数。
但是,为什么会有不同呢? (range)的实现细节是什么?和 (range 999)产生的序列有什么不同?
  • 范围在 clojure.core 中实现.
  • (range)定义为 (iterate inc' 0) .
  • 最终迭代的功能由 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)不认为这种情况普遍到足以保证优化。
  • 关于clojure - 理解 Clojure 惰性序列的新手问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68329882/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com