【问题标题】:how to build a chunked lazy-seq that blocks?如何构建一个阻塞的分块惰性序列?
【发布时间】:2020-12-07 05:20:15
【问题描述】:

我想使用 chunked cons 或其他方式来创建一个阻止的lazy-seq。给定来源:

(defn -source- [] (repeatedly (fn [] (future (Thread/sleep 100) [1 2]))))

(take 2 (-source-))
;; => (<future> <future>)

我想要一个名为 injest 的函数,其中:

(take 3 (injest (-source-)))
=> [;; sleep 100
    1 2 
    ;; sleep 100
    1]

(take 6 (injest (-source-)))
=> [;; sleep 100
    1 2 
    ;; sleep 100
    1 2 
    ;; sleep 100
    1 2]

;; ... etc ...

我将如何编写这个函数?

【问题讨论】:

    标签: clojure lazy-sequences


    【解决方案1】:

    当您使用它时,此来源会自然阻塞,因此您不必做任何非常花哨的事情。只需(mapcat deref)

    (doseq [x (take 16 (mapcat deref (-source- )))]
      (println {:value x :time (System/currentTimeMillis)}))
    {:value 1, :time 1597725323091}
    {:value 2, :time 1597725323092}
    {:value 1, :time 1597725323092}
    {:value 2, :time 1597725323093}
    {:value 1, :time 1597725323093}
    {:value 2, :time 1597725323093}
    {:value 1, :time 1597725323194}
    {:value 2, :time 1597725323195}
    {:value 1, :time 1597725323299}
    {:value 2, :time 1597725323300}
    {:value 1, :time 1597725323406}
    {:value 2, :time 1597725323406}
    {:value 1, :time 1597725323510}
    {:value 2, :time 1597725323511}
    

    请注意前几件物品是如何同时出现的,然后每对物品都按您预期的时间交错排列?这是由于众所周知的(?)事实,出于性能原因,apply(以及因此使用apply concat 实现的mapcat)比必要的更急切。如果即使在前几项上获得正确的延迟对您很重要,您可以简单地实现自己的 apply concat 版本,它不会针对短输入列表进行优化。

    (defn ingest [xs]
      (when-let [coll (seq (map (comp seq deref) xs))]
        ((fn step [curr remaining]
           (lazy-seq
             (cond curr (cons (first curr) (step (next curr) remaining))
                   remaining (step (first remaining) (next remaining)))))
          (first coll) (next coll))))
    

    A. cmets 中的 Webb 提出了一个等效但更简单的实现:

    (defn ingest [coll]
      (for [batch coll,
            item @batch]
        item))
    

    【讨论】:

    • 我认为问题在于如果我只想拿 10 件物品。因为 mapcat 很懒,所以会阻塞。
    • 我完全不明白你的评论。如何阻止问题?这正是你所说的你想要的。
    • 我刚刚阅读了最初的评论。我错了两次:1)我的意思是写because mapcat isn't lazy。 2) 我尝试了(-&gt;&gt; (mapcat deref (-source-)) (take 10)),它返回 10 个值而不等待其余的值,所以 mapcat 实际上是懒惰的。对不起。
    • 您也可以在这里写(take 16 (for [x (map deref (-source-)) y x] y)) 进行延迟连接吗?
    • for 也会进行分块。这与mapcat 没有什么不同。所以它可以工作,但没有我们正在寻找的惰性属性。
    【解决方案2】:

    您可以通过迭代状态机来解决它。我认为这不会受到其他人指出的与 apply 相关的优化的影响,但我不确定这种方法是否可能存在其他问题:

    (defn step-state [[current-element-to-unpack input-seq]]
      (cond
        (empty? input-seq) nil
        (empty? current-element-to-unpack) [(deref (first input-seq)) (rest input-seq)]
        :default [(rest current-element-to-unpack) input-seq]))
    
    (defn injest [input-seq]
      (->> [[] input-seq]
           (iterate step-state)
           (take-while some?)
           (map first)
           (filter seq)
           (map first)))
    

    【讨论】:

    • 这很整洁。我最终使用了github.com/erdos/erdos.yield,但可能会改成这个。
    • 我认为你的step-state 解决方案实际上是通过尝试使用内置的惰性序列组合器来完成所有事情而变得更加复杂。从原始惰性序列和递归构建东西通常变得更简单,就像我的回答一样。
    • @amalloy 一位智者曾经说过:“好的设计是将事物分开”。我知道如何将我的解决方案分开。不确定你的。 :-)
    【解决方案3】:

    我认为您只需 deref'ing 惰性序列的元素就可以了,只需强制消耗您需要的条目,如下所示:

    (defn -source- [] (repeatedly (fn [] (future (Thread/sleep 100) [1 2]))))
    
    (defn injest [src]
      (map deref src))
    
    ;; (time (dorun (take 3 (injest (-source-)))))
    ;; => "Elapsed time: 303.432003 msecs"
    
    ;; (time (dorun (take 6 (injest (-source-)))))
    ;; => "Elapsed time: 603.319103 msecs"
    

    另一方面,我认为根据项目的数量,最好避免创建大量期货并使用lazy-seq,这取决于元素的索引可能会阻塞一段时间。

    【讨论】:

    • 我怀疑期货在这里只是一个说明性的工具,一种在从输入中实现项目时明确的方式,并确保我们不会过度消费。
    猜你喜欢
    • 2013-11-28
    • 1970-01-01
    • 2013-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-14
    相关资源
    最近更新 更多