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