Pregunta

¿Cómo debería controlar el progreso de una función asignada en clojure?

Al procesar los registros en un lenguaje imperativo que a menudo se imprime un mensaje de vez en cuando para indicar cómo las cosas han ido muy lejos, por ejemplo, informar cada 1000 registros. Esencialmente, esto está contando repeticiones de bucle.

Me preguntaba qué enfoques que podría tomar a esto en clojure donde estoy mapear una función sobre mi secuencia de registros. En este caso, la impresión del mensaje (e incluso llevar la cuenta de los avances) parecen ser esencialmente efectos secundarios.

Lo que he encontrado hasta el momento se ve así:

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

Por ejemplo:

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)

¿Hay otros (mejor) maneras de lograr este efecto?

¿Hay trampas en lo que estoy haciendo? (Creo que estoy preservando la pereza y no teniendo la cabeza, por ejemplo.)

¿Fue útil?

Solución

Lo bueno de clojure es que se puede adjuntar el reporte de los datos en sí en lugar del código que hace la computación. Esto le permite separar estas partes lógicamente distintas. Aquí es un trozo de mi misc.clj que encuentro yo uso en casi todos los proyectos:

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

luego envolver el reportero alrededor de sus datos y luego pasar el resultado a la función de procesamiento.

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

Otros consejos

Yo probablemente realizar la notificación en un agente. Algo como esto:

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

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

No sé de ningún formas existentes de hacer eso, tal vez sería una buena idea para buscar documentación clojure.contrib mirar si ya hay algo. Mientras tanto, he mirado a su ejemplo y se aclaró que hasta un poco.

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

Usted está dirigiendo en la dirección correcta, a pesar de que este ejemplo es demasiado simple. Esto me dio una idea acerca de una versión más generalizada de la función de informe de progreso. Esta función podría tener una función en forma de mapa, la función a ser mapeado, una función de informe y un conjunto de colecciones (o un valor semilla y una colección para las pruebas reducir).

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

La SEC? parte es que no sólo para reducir su uso con la que no lo hace necesariamente devuelve un secuencia. Con esta función, podemos reescribir su ejemplo como este:

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)

Prueba de la función de filtro:

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)

Y aunque la función de reducir:

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

He tenido este problema con algunas aplicaciones de ejecución lenta (por ejemplo ETL base de datos, etc.). Lo resuelto mediante la adición de la función (tupelo.misc/dot ...) a la biblioteca Tupelo . Muestra:

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

Salida:

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

documentación de la API para el espacio de nombres tupelo.misc se puede encontrar aquí .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top