惰性序列不延迟计算

Lazy seqs not deferring computation

我目前正在阅读 Clojure for the Brave and True 这本书,试图学习这门语言,但我对 lazy seqs 有点着迷,并且恐怕the book does a poor job explaining them。但是,根据这本书,是这样的:

(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 等上下文中。

惰性序列默认分块。所以实际值是以 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 不创建。如果我们再次看同一个例子,这次使用 iterate 而不是 map 生成 seq,我们得到一个非分块序列:

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 的时间与任何人一样长,而且从未见过这会对设计不佳的东西产生影响的情况。