【问题标题】:Idiomatic clojure for progress reporting?用于进度报告的惯用 clojure?
【发布时间】:2011-01-02 15:14:14
【问题描述】:

我应该如何监控 clojure 中映射函数的进度?

在使用命令式语言处理记录时,我经常会打印一条消息以指示事情进展的程度,例如每 1000 条记录报告一次。本质上这是计算循环重复次数。

我想知道在 clojure 中我可以采取什么方法来解决这个问题,我在我的记录序列上映射一个函数。在这种情况下,打印消息(甚至记录进度)似乎基本上是副作用。

到目前为止我想出的看起来像:

(defn report
  [report-every val cnt]
  (if (= 0 (mod cnt report-every))
    (println "Done" cnt))
    val)

(defn report-progress
  [report-every aseq]
  (map (fn [val cnt] 
          (report report-every val cnt)) 
       aseq 
       (iterate inc 1)))

例如:

user> (doall (report-progress 2 (range 10)))
Done 2
Done 4
Done 6
Done 8
Done 10
(0 1 2 3 4 5 6 7 8 9)

还有其他(更好的)方法可以达到这种效果吗?

我在做什么有什么陷阱吗? (我想我是在保持懒惰,例如不抱头。)

【问题讨论】:

    标签: functional-programming lisp clojure imperative-programming


    【解决方案1】:

    clojure 的优点在于您可以将报告附加到数据本身,而不是执行计算的代码。这允许您分离这些逻辑上不同的部分。这是我的 misc.clj 中的一部分,我发现我几乎在每个项目中都会用到它:

    (defn seq-counter 
      "calls callback after every n'th entry in sequence is evaluated. 
      Optionally takes another callback to call once the seq is fully evaluated."
      ([sequence n callback]
         (map #(do (if (= (rem %1 n) 0) (callback)) %2) (iterate inc 1) sequence))
      ([sequence n callback finished-callback]
         (drop-last (lazy-cat (seq-counter sequence n callback) 
                      (lazy-seq (cons (finished-callback) ())))))) 
    

    然后将报告器包裹在您的数据周围,然后将结果传递给处理函数。

    (map process-data (seq-counter inc-progress input))
    

    【讨论】:

    • 我想我在做类似上面的事情,将报告附加到可以做任何事情的 seq 上。我设想将它附加到一系列结果中,但它同样可以是输入序列。不过,您的代码要好得多。我没有进展(请原谅双关语)对报告消息(或更通用的函数)使用回调,我正在为每个值调用报告函数。
    • 有没有你为 misc.clj 分享的地方?我肯定会从其他有用的想法和实现中受益,比如 seq-counter
    • 是的,它与您最初的示例完全相同,我在没有正确理解问题的情况下对“哦,那就是 misk.clj”有点快。 code.google.com/p/cryptovide/source/browse/src/com/cryptovide/….
    • 应用示例好像有错字。我正在扩展示例以探索我的理解。为了利用解决方案领域的格式化功能,我必须在上面的答案中添加我的修复。同时,我也发现了意想不到的输出惊喜。请帮助查看我误解了什么。谢谢,
    • 我将上述解决方案中的概念概括为一个库,以便将其轻松放入项目中。希望这可以帮助某人。 https://github.com/tmountain/seq-peek.
    【解决方案2】:

    我遇到过一些运行缓慢的应用程序(例如数据库 ETL 等)的问题。我通过添加函数(tupelo.misc/dot ...)to the tupelo library 解决了这个问题。示例:

    (ns xxx.core 
      (:require [tupelo.misc :as tm]))
    
    (tm/dots-config! {:decimation 10} )
    (tm/with-dots
      (doseq [ii (range 2345)]
        (tm/dot)
        (Thread/sleep 5)))
    

    输出:

         0 ....................................................................................................
      1000 ....................................................................................................
      2000 ...................................
      2345 total
    

    tupelo.misc 命名空间 can be found here 的 API 文档。

    【讨论】:

      【解决方案3】:

      我可能会在代理中执行报告。像这样的:

      (defn report [a]
        (println "Done " s)
        (+ 1 s))
      
      (let [reports (agent 0)]
        (map #(do (send reports report)
                  (process-data %))
             data-to-process)
      

      【讨论】:

      • 这是一个有趣的方法。奇怪的是,如果我在 emacs 中使用 slime-mode,但报告不会显示在我的 repl 中,但会以正常的 repl 打印。
      • 经过进一步思考,我可以在发送给代理的函数中增加内容。打印进度可以是访问代理状态的 repl 上的常规函数​​。
      • 其实很好。事实上,如果你要更新一个 GUI,你可能不得不在主线程中进行(或者推迟到主线程,dispatchLater 等)。
      【解决方案4】:

      我不知道任何现有的这样做的方法,也许浏览 clojure.contrib 文档以查看是否已经存在一些东西是个好主意。同时,我查看了您的示例并对其进行了一些清理。

      (defn report [cnt]
        (when (even? cnt)
          (println "Done" cnt)))
      
      (defn report-progress []
        (let [aseq (range 10)]
          (doall (map report (take (count aseq) (iterate inc 1))))
          aseq))
      

      您正朝着正确的方向前进,尽管这个示例太简单了。这让我对您的报告进度功能的更通用版本有了一个想法。该函数将采用一个类似 map 的函数、要映射的函数、一个报告函数和一组集合(或一个种子值和一个用于测试 reduce 的集合)。

      (defn report-progress [m f r & colls]
        (let [result (apply m
                       (fn [& args]
                         (let [v (apply f args)]
                           (apply r v args) v))
                       colls)]
          (if (seq? result)
            (doall result)
            result)))
      

      序列?部分仅用于 reduce 而不是 必然返回一个序列。有了这个功能,我们可以重写你的 像这样的例子:

      user> 
      (report-progress
        map
        (fn [_ v] v)
        (fn [result cnt _]
          (when (even? cnt)
            (println "Done" cnt)))
        (iterate inc 1)
        (range 10))
      
      Done 2
      Done 4
      Done 6
      Done 8
      Done 10
      (0 1 2 3 4 5 6 7 8 9)
      

      测试过滤功能:

      user> 
      (report-progress
        filter
        odd?
        (fn [result cnt]
          (when (even? cnt)
            (println "Done" cnt)))
        (range 10))
      
      Done 0
      Done 2
      Done 4
      Done 6
      Done 8
      (1 3 5 7 9)
      

      甚至是reduce函数:

      user> 
      (report-progress
        reduce
        +
        (fn [result s v]
          (when (even? s)
            (println "Done" s)))
        2
        (repeat 10 1))
      
      Done 2
      Done 4
      Done 6
      Done 8
      Done 10
      12
      

      【讨论】:

      • 我认为你不明白我试图用 'doall' 做什么(对于糟糕和不清楚的代码感到抱歉)。我只是使用 doall 来测试 repl 的报告,以强制报告处理整个序列(否则它会被懒惰地评估)。 doall 不是我尝试的报告功能或预期的序列处理的一部分。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-13
      • 1970-01-01
      相关资源
      最近更新 更多