Réducteurs
Les réducteurs offrent une approche alternative à l'utilisation des séquences pour manipuler les collections Clojure standard. Les fonctions de séquence sont généralement appliquées de manière paresseuse, séquentiellement, générant des résultats intermédiaires et dans un seul processus léger. Cependant, de nombreuses fonctions de séquence (comme `map` et `filter`) pourraient conceptuellement être appliquées en parallèle, ce qui permettrait d'obtenir un code plus rapide à mesure que le nombre de coeurs augmente. Pour plus de détails sur la logique des réducteurs, consultez les articles de blog originaux.
Un réducteur est la combinaison d'une collection réductible (une collection qui sait comment se réduire) et d'une fonction de réduction (la «recette» des opérations à effectuer lors de la réduction). Les opérations de séquence standard sont remplacées par de nouvelles versions qui ne réalisent pas l'opération, mais se contentent de transformer la fonction de réduction. L'exécution des opérations est différée jusqu'à la réduction finale. Ceci élimine les résultats intermédiaires et l'évaluation paresseuse caractéristiques des séquences.
De plus, certaines collections (vecteurs persistants et maps) sont pliables. L'opération de pliage sur un réducteur exécute la réduction en parallèle en procédant comme suit :
- Partitionnement de la collection réductible selon une granularité spécifiée (par défaut : 512 éléments)
- Application de la réduction à chaque partition
- Combinaison récursive de chaque partition à l'aide du cadre d'application fork/join de Java.
Si une collection ne prend pas en charge le pliage, elle utilisera à la place une réduction non parallèle.
Réduction et pliage
L'espace de noms clojure.core.reducers (aliasé ici r) fournit une fonction r/reduce alternative.
|
(r/reduce f coll) (r/reduce f init coll) |
La version avec réducteurs diffère en ce que :
- Les collections de cartes sont réduites avec reduce-kv.
- Lorsque l'initialisation n'est pas fournie, la fonction f est appelée sans argument pour produire une valeur d'identité.
- Remarque : la fonction f peut être appelée plusieurs fois pour produire la valeur d'identité.
En général, la plupart des utilisateurs n'appelleront pas directement `r/reduce` et privilégieront `r/fold`, implémentant une réduction et une combinaison parallèles. Cependant, il peut être utile d'effectuer une réduction immédiate avec moins de résultats intermédiaires.
|
(r/fold reducef coll) (r/fold combinef reducef coll) (r/fold n combinef reducef coll) |
La fonction `r/fold` prend une collection réductible et la partitionne en groupes d'environ n éléments (512 par défaut). Chaque groupe est réduit à l'aide de la fonction `reducef`. Cette dernière est appelée sans argument afin de produire une valeur d'identité dans chaque partition. Les résultats de ces réductions sont ensuite réduits avec la fonction `combinef` (qui utilise `reducef` par défaut). Lorsqu'elle est appelée sans argument, `combinef` doit produire son élément d'identité ; cette opération sera effectuée plusieurs fois. Les opérations peuvent être exécutées en parallèle. L'ordre des résultats est préservé.
Les fonctions suivantes (analogues aux versions séquentielles) créent des réducteurs à partir d'une collection réductible ou pliable : `r/map`, `r/mapcat`, `r/filter`, `r/remove`, `r/flatten`, `r/take-while`, `r/take` et `r/drop`. Aucune de ces fonctions ne transforme la collection source. Pour obtenir un résultat cumulé, vous devez utiliser `r/reduce` ou `r/fold`. Pour produire une collection de sortie, utilisez clojure.core/into pour choisir le type de collection ou le r/foldcat fourni pour produire une collection réductible, pliable, séquentielle et comptée.
Utilisation des réducteurs
Utilisez la fonction `fold` pour effectuer une somme avec l'opérateur `+`.
- (require '[clojure.core.reducers :as r])
- (r/fold + (r/filter even? (r/map inc [1 1 1 2])))
- ;=> 6
Utiliser pour produire une collection finale :
- (into [] (r/filter even? (r/map inc (range 100000))))
Ou r/foldcat :
- (r/foldcat (r/filter even? (r/map inc (range 100000))))
Spécifiez une fonction de réduction et une fonction de combinaison avec fold :
- (defn count-words
- ([] {})
- ([freqs word]
- (assoc freqs word (inc (get freqs word 0)))))
-
- (defn merge-counts
- ([] {})
- ([& m] (apply merge-with + m)))
-
- (defn word-frequency [text]
- (r/fold merge-counts count-words (clojure.string/split text #"\s+")))
Quand utiliser
Utilisez la forme réductrice de ces opérations pour :
- L'application immédiate et efficace d'une transformation en plusieurs étapes
- Éviter les problèmes de ressources d'entrées/sorties non utilisées (comme avec les séquences paresseuses)
Utilisez la fonction `fold` lorsque :
- Les données sources peuvent être générées et stockées en mémoire.
- Le travail à effectuer consiste en des calculs (et non en des opérations d'entrée/sortie ou bloquantes).
- Le nombre d'éléments de données ou le travail à effectuer est important.