【问题标题】:Lazy seqs not deferring computation惰性序列不延迟计算
【发布时间】:2016-03-11 23:30:38
【问题描述】:

我目前正在阅读 Clojure for the Brave and True 一书,试图学习这门语言,但我有点沉迷于懒惰的 seqs,恐怕@ 987654321@。但是,根据这本书,是这样的:

(defn wait-for-a-bit [arg]
  (Thread/sleep 1000))

(defn get-map [seq]
  (map wait-for-a-bit seq))

(time (first (get-map [1 2 3 4 5 6 7 8 9 0])))

应该只需要大约一秒钟的时间来处理,因为惰性序列的值在被访问之前不会被计算(实现?)。但是,当我运行上面的代码时,大约需要十秒钟,所以很明显,延迟计算没有发生。我查看了clojuredocs.org 上的文档,我想我了解lazy-seq,但我想只是不在map、reduce 等的上下文中。

【问题讨论】:

  • 我看到了类似的。然后我尝试了(time (first (get-map (iterate inc 1000)))),它给了我更直观的 1 秒响应。

标签: clojure lazy-evaluation lazy-sequences


【解决方案1】:

默认情况下,惰性序列是分块的。所以实际值是以 30 个左右的块计算的。这大大减少了处理它们时的上下文切换开销。

在这里,我将定义一个 100 项长的序列,并查看前两项:

hello.core> (def foo (map #(do (println "calculating " %)
                               %)
                          (range 100)))
#'hello.core/foo
hello.core> (first foo)
calculating  0
calculating  1
calculating  2
calculating  3
calculating  4
calculating  5
calculating  6
calculating  7

...

calculating  26
calculating  27
calculating  28
calculating  29
calculating  30
calculating  31
0
hello.core> (second foo)
1

这表明它在第一次实现任何项目时计算第一个块。

有些序列是分块的,而另一些则不是。由最初创建 seq 的函数决定是否可以分块。 range 创建分块序列,而 iterate 没有。如果我们再次查看相同的示例,这次使用迭代而不是映射生成序列,我们得到一个非分块序列:

hello.core> (def foo (map #(do (println "calculating " %)
                               %)
                          (take 100 (iterate inc 0))))
#'hello.core/foo
hello.core> (first foo)
calculating  0
0
hello.core> (second foo)
calculating  1
1

每个项目都是在阅读时计算的。从理论上讲,这会影响效率,尽管我一直在全职编写 Clojure,与任何人一样长,并且从未见过这样的案例,这会对原本设计不佳的东西产生影响。

【讨论】:

  • 肯定是map 尽可能分块,如the source code 所示,而不是一般的lazy-seq
  • map(以及大多数转换序列的函数)如果它已经存在,则保留它的块度,尽管它们不会将它添加到它不在原始序列中的位置。有几种实现惰性序列的类型,有些是分块的,有些不是
猜你喜欢
  • 2011-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多