33

¿Cómo debo controlar el progreso de una función mapeada en clojure?Clojure idiomático para informes de progreso?

Al procesar registros en un idioma imperativo, a menudo imprimo un mensaje de vez en cuando para indicar qué tan lejos han ido las cosas, p. informando cada 1000 registros. Esencialmente esto es contar repeticiones de bucle.

Me preguntaba qué enfoques podría llevar a esto en Clojure donde estoy mapeando una función sobre mi secuencia de registros. En este caso, imprimir el mensaje (e incluso contar el progreso) parece ser esencialmente un efecto secundario.

Lo que he encontrado hasta el momento parece:

(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 algún escollo en lo que estoy haciendo? (Creo que estoy conservando la pereza y no tomo la cabeza, por ejemplo.)

Respuesta

32

Lo bueno de clojure es que puede adjuntar el informe a los datos 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)) 
+1

Creo que estoy haciendo algo crudamente similar arriba, adjuntando los informes a un seq con el que se podría hacer cualquier cosa. Estaba visualizando adjuntarlo a una secuencia de resultados, pero también podría ser la secuencia de entradas. Sin embargo, su código es mucho más agradable. No había progresado (perdón por el juego de palabras) a usar una devolución de llamada para el mensaje de informe (o una función más general) y estaba llamando a la función de informe para cada valor. –

+1

¿Hay algún lugar que compartas para misc.clj? Sin duda me beneficiaría de ver otras ideas e implementaciones de cosas útiles como seq-counter –

+1

sí, es realmente lo mismo que tu ejemplo inicial, fui un poco rápido en el "ohh eso en misk.clj" sin comprender correctamente la pregunta . http://code.google.com/p/cryptovide/source/browse/src/com/cryptovide/misc.clj. –

4

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, miré tu ejemplo y lo aclare 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)) 

Va en la dirección correcta, aunque este ejemplo es demasiado simple. Esto me dio una idea sobre una versión más generalizada de su función de informe de progreso. Esta función tomaría una función tipo mapa, la función a mapear, una función de informe y un conjunto de colecciones (o un valor de inicialización y una colección para probar 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 seq? la parte está allí solo para su uso con reduce, que no hace necesariamente devuelve una secuencia. Con esta función, podemos reescribir el ejemplo así:

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) 

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

No creo que haya entendido lo que estaba tratando de hacer con 'doall' (lo siento por el código pésimo y poco claro). Solo estaba usando doall para probar los informes en el repl para forzar la presentación de informes sobre el procesamiento de la secuencia completa (de lo contrario, sería evaluado de forma perezosa). doall no fue parte de mi función de informe de intento ni del procesamiento de secuencia previsto. –

6

que probablemente realizar la informando 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) 
+1

Ese es un enfoque interesante. Curiosamente, el informe no aparece en mi respuesta si uso el modo limo en emacs, pero imprime en una versión normal. –

+1

En una mayor reflexión, podría simplemente incrementar las cosas en la función enviada al agente. La impresión del progreso podría ser una función regular en la réplica que accede al estado del agente. –

+1

Buen punto en realidad. De hecho, si está actualizando una GUI, probablemente tenga que hacerlo en el hilo principal (o diferirlo al hilo principal, dispatchLater, etc.) de todos modos. – Dan

0

He tenido este problema con algunas aplicaciones de ejecución lenta (por ejemplo, ETL de base de datos, etc.).Lo resolví agregando la función (tupelo.misc/dot ...)to the tupelo library. 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 can be found here.

Cuestiones relacionadas