【问题标题】:When are the different elements of a lazy sequence realized in clojure?clojure 中何时实现惰性序列的不同元素?
【发布时间】:2012-05-25 07:49:05
【问题描述】:

我试图了解 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 的开头,而不必了解整个事情。怎么回事?

【问题讨论】:

  • 只是一个小评论:map 不应该带一个有副作用的函数,否则,你可能会收到意想不到的行为。每当您需要产生一些副作用时,请改用doseq

标签: clojure lazy-sequences


【解决方案1】:

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

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

【讨论】:

  • 两年前 Fogus 所说的任何官方回避机制都没有实现。但是,新的 fold/reduce 库的工作将通过允许您以比分块更快但不会实现任何不必要元素的方式执行大量当前惰性序列工作来缓解此问题。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    惰性序列是我们在需要时对序列进行评估的序列。 (因此懒惰)。一旦结果被评估,它就会被缓存,以便可以重复使用(我们不必再次做这项工作)。如果您尝试实现尚未评估的序列项目,clojure 会评估它并将值返回给您。但是,它也做了一些额外的工作。它预计您可能想要评估序列中的下一个元素,并为您执行此操作。这样做是为了避免一些性能开销,其确切性质超出了我的技能水平。因此,当您说 (first 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 个。那是因为第一次调用实际上最终会评估序列中的多个元素。

    希望这个解释足够清楚。

    【讨论】:

      【解决方案4】:

      我认为它是由 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
      

      【讨论】:

        猜你喜欢
        • 2011-03-15
        • 2019-01-28
        • 1970-01-01
        • 1970-01-01
        • 2010-12-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多