【问题标题】:Realization timing of lazy sequence惰性序列的实现时序
【发布时间】:2012-11-19 13:40:34
【问题描述】:
(defn square [x]
  (do
    (println (str "Processing: " x))
    (* x x)))

(println (map square '(1 2 3 4 5)))

为什么是输出

(Processing: 1 
Processing: 2 
1 Processing: 3 
4 Processing: 4 
9 Processing: 5 
16 25)

不是

(Processing: 1
1 Processing: 2
4 Processing: 3 
9 Processing: 4 
16 Processing: 5 
25)

?

【问题讨论】:

    标签: clojure lazy-sequences


    【解决方案1】:

    因为map 是懒惰的。它在封面下使用lazy-seq,即pre-caches the result of rest。因此,当您的代码获取 map 序列的第一个值时,您会看到两条 println 语句出现。

    另请参阅此博文:Lazy Sequences

    【讨论】:

    • lazy-seq 不会预先缓存 rest 的结果。您链接到的文章是多年前对一些正在进行的工作的描述,但从未得到应用,试图解决不再存在的问题。爱丽丝的解释是正确的。 edit 好吧,工作确实被应用了,但我不明白你是如何得到任何预缓存的印象 - 如果有的话,懒惰的 seqs 根本不会懒惰,因为它们会递归地“预缓存”整个惰性序列!
    • 好的,所以我误用了“预缓存”这个词,进一步被那篇博文误导了。我的意思是“在打印之前评估前两个元素。”
    【解决方案2】:

    println 在其implementation 中使用[[x & xs] xs] 解构形式。这相当于[x (first xs), xs (next xs)]next is less lazy than rest,所以它在打印第一个之前实现了这两个。

    例如,

    => (defn fn1 [[x & xs]] nil)
    #'user/fn1
    => (fn1 (map square '(1 2 3 4 5)))
    Processing: 1
    Processing: 2
    nil
    

    【讨论】:

      【解决方案3】:

      你和我一样学代码sn-ps吗?这里有一些。

      我们看一下map的文档。

      user=> (doc map)
      -------------------------
      clojure.core/map
      ([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
        Returns a lazy sequence consisting of the result of applying f to the
        set of first items of each coll, followed by applying f to the set
        of second items in each coll, until any one of the colls is
        exhausted.  Any remaining items in other colls are ignored. Function
        f should accept number-of-colls arguments.
      nil
      

      map 返回一个惰性序列(您应该已经阅读了@noahz 给出的引用)。要完全实现惰性序列(这通常不是一个好习惯,因为惰性序列可能是无限的,因此永远不会结束),您可以使用dorundoall

      user=> (doc dorun)
      -------------------------
      clojure.core/dorun
      ([coll] [n coll])
        When lazy sequences are produced via functions that have side
        effects, any effects other than those needed to produce the first
        element in the seq do not occur until the seq is consumed. dorun can
        be used to force any effects. Walks through the successive nexts of
        the seq, does not retain the head and returns nil.
      nil
      user=> (doc doall)
      -------------------------
      clojure.core/doall
      ([coll] [n coll])
        When lazy sequences are produced via functions that have side
        effects, any effects other than those needed to produce the first
        element in the seq do not occur until the seq is consumed. doall can
        be used to force any effects. Walks through the successive nexts of
        the seq, retains the head and returns it, thus causing the entire
        seq to reside in memory at one time.
      nil
      

      尽管它们看起来很相似,但它们并不相似 - 请注意它们对待已实现序列的头部的方式不同。

      有了这些知识,您可以使用doall 影响映射惰性序列的行为方式。

      user=> (defn square [x]
        #_=>   (println (str "Processing: " x))
        #_=>   (* x x))
      #'user/square
      user=> (doall (map square '(1 2 3 4 5)))
      Processing: 1
      Processing: 2
      Processing: 3
      Processing: 4
      Processing: 5
      (1 4 9 16 25)
      

      您可能已经注意到,我还更改了 square 函数的定义,因为您不需要在函数内部使用 do(它隐含在 defn 宏中)。

      Clojure Programming这本书里,有句你可能喜欢'(1 2 3 4 5)的例子:

      "对于这种情况,大多数人只是简单地使用向量字面量,其中成员表达式 将始终被评估。”

      与其复制相关部分来支持此声明,我更愿意推荐这本书,因为它值得花时间和金钱。

      【讨论】:

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