33

我應該如何監視clojure中映射函數的進度?進展報告的習慣性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) 

正在實現這種效果的還有其他(更好)的方式?

我在做什麼有什麼陷阱? (我認爲我保留了懶惰,而不是頭腦風暴)

回答

32

關於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)) 
+1

我想我正在做一些與上面類似的事情,將報告附加到可以完成任何事情的seq。我設想將它附加到一系列結果中,但它同樣可以是輸入序列。 雖然你的代碼更好。我沒有取得進展(赦免雙關語言)使用報告消息的回調(或更通用的功能),並且我正在爲每個值調用報告功能。 – 2010-01-07 20:33:53

+1

是否有你分享misc.clj的任何地方?我肯定會從看到seq-counter – 2010-01-07 20:36:42

+1

等有用的東西的其他想法和實現中受益,是的,它與您的初始示例相同,我對「misk.clj中的ohh thats」有一點點了解,但沒有正確理解問題。 http://code.google.com/p/cryptovide/source/browse/src/com/cryptovide/misc.clj。 – 2010-01-07 22:05:35

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))) 

該seq?部分是隻爲使用減少,而不是 必然返回一個序列。有了這個功能,我們可以重寫你的 例子是這樣的:

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) 

即使精簡函數:

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 
+1

我不認爲你明白我想要做什麼'doall'(對於糟糕和不明確的代碼抱歉)。我只是在使用doall來測試repl上的報告,以強制報告處理整個序列(否則它將被懶惰地評估)。多爾不是我試圖報告功能或預期序列處理的一部分。 – 2010-01-09 19:11:59

6

我可能會執行在代理中報告。這樣的事情:

(defn report [a] 
    (println "Done " s) 
    (+ 1 s)) 

(let [reports (agent 0)] 
    (map #(do (send reports report) 
      (process-data %)) 
     data-to-process) 
+1

這是一個有趣的方法。奇怪的是,如果我在emacs中使用slime-mode,但是在正常repl中打印,則報告不會顯示在我的repl中。 – 2010-01-08 21:18:22

+1

經過進一步的思考,我可以增加發送給代理的函數中的內容。進度的打印可以是訪問代理狀態的repl上的常規功能。 – 2010-01-08 21:41:48

+1

其實好點。事實上,如果你正在更新一個GUI,你可能必須在主線程中完成它(或者推遲到主線程,dispatchLater等)。 – Dan 2010-01-11 14:00:58

0

我有一些運行緩慢的應用程序(例如數據庫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文檔。