4

我试图了解 clojure 的惰性序列何时是惰性的,何时发生工作,以及我如何影响这些事情。

user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 4)))
#'user/lz-seq
user=> (let [[a b] lz-seq])
fn call!
fn call!
fn call!
fn call!
nil

我希望在这里只看到两个“fn call!”。有没有办法管理它?无论如何,继续进行毫无疑问只需要一次评估的事情:

user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 4)))
#'user/lz-seq
user=> (first lz-seq)
fn call!
fn call!
fn call!
fn call!
0

first适合惰性序列?

user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 4)))
#'user/lz-seq
user=> (take 1 lz-seq)
(fn call!
fn call!
fn call!
fn call!
0)

在这一点上,我完全不知道如何访问我的玩具 lz-seq 的开头而不必了解整个事情。这是怎么回事?

4

4 回答 4

2

我相信表达式会产生一个分块的序列。尝试在范围表达式中将 4 替换为 10000 - 您会在第一个 eval 上看到类似 32 次调用的内容,这是块的大小。

于 2012-05-25T08:04:32.847 回答
2

Clojure 的序列是惰性的,但为了提高效率也是分块的,一次实现 32 个结果块。

=>(def lz-seq (map #(do (println (str "fn call " %)) (identity %)) (range 100)))
=>(first lz-seq)

fn call 0
fn call 1
...
fn call 31
0

一旦你先越过 32 边界,也会发生同样的事情

=>(nth lz-seq 33)
fn call 0
fn call 1
...
fn call 63
33

对于每次实现都需要完成大量工作的代码,Fogus提供了一种解决分块的方法,并提示可能正在进行控制分块的官方方法。

于 2012-05-25T08:08:17.883 回答
0

惰性序列是我们在需要时对序列进行评估的序列。(因此懒惰)。一旦结果被评估,它就会被缓存,以便可以重复使用(我们不必再次做这项工作)。如果您尝试实现尚未评估的序列项目,clojure 会评估它并将值返回给您。但是,它也做了一些额外的工作。它预计您可能想要评估序列中的下一个元素,并为您执行此操作。这样做是为了避免一些性能开销,其确切性质超出了我的技能水平。因此,当您说 (first lz-seq) 时,它实际上会计算 seq 中的第一个以及接下来的几个元素。由于您的 println 语句是一个副作用,您可以看到正在发生的评估。现在,如果你说(第二个 lz-seq),

查看您的序列是否惰性的更好方法是:

user=> def lz-seq (map #(do (println "fn call!") (identity %)) (range 400))
#'user/lz-seq
user=> (first lz-seq)

这将打印几个“fn call!” 声明,但不是全部 400 个。那是因为第一次调用实际上最终会评估序列中的多个元素。

希望这个解释足够清楚。

于 2012-05-25T08:08:12.763 回答
0

我认为它是由 repl 进行的某种优化。我的 repl 一次缓存 32 个。

user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 100))
#'user/lz-seq
user=> (first lz-seq)
prints 32 times
user=> (take 20 lz-seq)
does not print any "fn call!"
user=> (take 33 lz-seq)
prints 0 to 30, then prints 32 more "fn call!"s followed by 31,32
于 2012-05-25T08:21:07.763 回答