Идиоматические 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)
Существуют ли другие (лучшие) способы достижения этого эффекта?
Есть ли какие-то подводные камни в том, что я делаю? (Я думаю, что сохраняю лень и не держу голову, например.)4 ответа:
Самое замечательное в 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))
Я, вероятно, буду выполнять отчет в Агенте. Что-то вроде этого:
(defn report [a] (println "Done " s) (+ 1 s)) (let [reports (agent 0)] (map #(do (send reports report) (process-data %)) data-to-process)
Я не знаю ни одного существующего способа сделать это, возможно, было бы неплохо просмотреть clojure.contrib документация, чтобы посмотреть, если там уже что-то есть. Тем временем я посмотрел на ваш пример и немного прояснил его.
Вы движетесь в правильном направлении, хотя этот пример слишком прост. Это дало мне представление о более обобщенной версии вашей функции отчета-прогресса. Эта функция будет принимать функцию, подобную карте, функцию, которая будет отображение, функция отчета и набор коллекций (или начальное значение и коллекция для тестирования reduce).(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))
(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? часть существует только для использования с 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
У меня была эта проблема с некоторыми медленно работающими приложениями (например, database ETL и т. д.). Я решил ее, добавив функцию
(tupelo.misc/dot ...)
в библиотеку Тьюпело . Пример:(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
API docs для tupelo.разное пространство имен можно найти здесь .