33

Comment surveiller la progression d'une fonction mappée dans clojure?Clojure idiomatique pour les rapports de progrès?

Lors du traitement d'enregistrements dans un langage impératif, j'imprime souvent un message de temps en temps pour indiquer à quel point les choses sont allées, par ex. rapports tous les 1000 enregistrements. Essentiellement, cela compte les répétitions de boucles. Je me demandais quelles approches je pourrais prendre à cela dans clojure où je suis la cartographie d'une fonction sur ma séquence d'enregistrements. Dans ce cas, l'impression du message (et même le compte de la progression) semblent être essentiellement des effets secondaires.

Ce que je suis venu avec l'air si loin comme:

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

Par exemple:

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) 

Y at-il d'autres (mieux) les moyens de parvenir à cet effet?

Y a-t-il des pièges dans ce que je fais? (Je pense que je suis de préserver la paresse et ne pas tenir la tête par exemple.)

Répondre

32

La grande chose à propos de clojure est que vous pouvez attacher le rapport à la donnée elle-même au lieu du code qui fait l'informatique. Cela vous permet de séparer ces parties logiquement distinctes. Voici un morceau de mon misc.clj que je trouve que je l'utilise dans à peu près tous les projets:

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

puis enveloppez le journaliste autour de vos données et les transmettre ensuite le résultat à la fonction de traitement.

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

Je pense que je fais quelque chose de grossièrement similaire ci-dessus, en attachant le rapport à un seq avec lequel tout peut être fait. J'envisageais de l'attacher à une suite de résultats mais cela pourrait tout aussi bien être la séquence d'entrées. Votre code est bien plus agréable. Je n'avais pas progressé (pardonnez le jeu de mots) en utilisant un rappel pour le message de rapport (ou une fonction plus générale) et j'appelais la fonction de rapport pour chaque valeur. –

+1

Y a-t-il quelque part que vous partagez pour misc.clj? Je profiterais certainement de voir d'autres idées et implémentations de trucs utiles comme seq-counter –

+1

oui c'est vraiment le même que ton premier exemple, j'étais un peu rapide sur le "ohh c'est dans misk.clj" sans bien comprendre la question . http://code.google.com/p/cryptovide/source/browse/src/com/cryptovide/misc.clj. –

4

Je ne connais pas de moyens existants de le faire, il serait peut-être une bonne idée de parcourir la documentation de clojure.contrib regarder s'il y a déjà quelque chose. En attendant, j'ai regardé votre exemple et l'ai éclairci un peu.

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

Vous vous dirigez dans la bonne direction, même si cet exemple est trop simple. Cela m'a donné une idée d'une version plus généralisée de votre fonction de rapport-progression. Cette fonction prendrait une fonction semblable à une carte, la fonction à mapper, une fonction de rapport et un ensemble de collections (ou une valeur de départ et une collection pour tester réduire).

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

Le seq? partie est là seulement pour une utilisation avec réduire qui ne renvoie nécessairement une séquence. Avec cette fonction, on peut réécrire votre exemple comme ceci:

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) 

Testez la fonction de filtre:

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) 

Et même la diminution de la fonction:

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

Je ne pense pas que vous avez compris ce que j'essayais de faire avec 'doall' (désolé pour le code moche et peu clair). J'utilisais simplement doall pour tester les rapports à la réplique pour forcer la génération de rapports sur le traitement de toute la séquence (sinon, elle serait évaluée paresseusement). Tout ne faisait pas partie de ma fonction de création de rapport ou de traitement de séquence. –

6

Je probablement effectuer la signaler dans un agent. Quelque chose comme ceci:

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

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

C'est une approche intéressante. Curieusement, le rapport n'apparaît pas dans mon repl si j'utilise slime-mode dans emacs mais imprime dans un repl normal. –

+1

Après réflexion, je pourrais simplement incrémenter des choses dans la fonction envoyée à l'agent. L'impression de la progression pourrait être une fonction régulière au repl qui accède à l'état de l'agent. –

+1

Bon point en fait. En fait, si vous mettez à jour une interface graphique, vous devez probablement le faire dans le thread principal (ou le reporter au thread principal, dispatchLater etc) de toute façon. – Dan

0

J'ai eu ce problème avec quelques applications à exécution lente (par exemple ETL de base de données, etc.).Je l'ai résolu en ajoutant la fonction (tupelo.misc/dot ...)to the tupelo library. Exemple:

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

Sortie:

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

docs API pour l'espace de noms tupelo.misc can be found here.

Questions connexes