什么是懒惰序列的不同元素何时在clojure中实现?

时间:2012-05-25 07:49:05

标签: clojure lazy-sequences

我试图理解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 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分: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提供了work around chunking的方法,并提供了一个控制分块的正式方法可能正在进行中。

答案 2 :(得分:0)

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

更好的方法是看到你的序列是懒惰的:

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

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

希望这个解释足够清楚。

答案 3 :(得分: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