web-dev-qa-db-fra.com

Quelqu'un peut-il m'expliquer Clojure Transducers en termes simples?

J'ai essayé de lire à ce sujet, mais je ne comprends toujours pas leur valeur ou ce qu'ils remplacent. Et rendent-ils mon code plus court, plus compréhensible ou quoi?

Mise à jour

Beaucoup de gens ont posté des réponses, mais ce serait bien de voir des exemples avec et sans transducteurs pour quelque chose de très simple, que même un idiot comme moi peut comprendre. À moins bien sûr que les transducteurs aient besoin d'un certain niveau élevé de compréhension, auquel cas je ne les comprendrai jamais :(

87
Zubair

Les transducteurs sont des recettes que faire d'une séquence de données sans savoir quelle est la séquence sous-jacente (comment le faire). Il peut s'agir de n'importe quel seq, canal asynchrone ou peut-être observable.

Ils sont composables et polymorphes.

L'avantage est que vous n'avez pas à implémenter tous les combinateurs standard chaque fois qu'une nouvelle source de données est ajoutée. Encore et encore. En tant qu'effet résultant, vous, en tant qu'utilisateur, pouvez réutiliser ces recettes sur différentes sources de données.

Mise à jour de l'annonce

Avant la version 1.7 de Clojure, vous aviez trois façons d'écrire des requêtes de flux de données:

  1. appels imbriqués
    (reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
  1. composition fonctionnelle
    (def xform
      (comp
        (partial filter odd?)
        (partial map #(+ 2 %))))
    (reduce + (xform (range 0 10)))
  1. macro de filetage
    (defn xform [xs]
      (->> xs
           (map #(+ 2 %))
           (filter odd?)))
    (reduce + (xform (range 0 10)))

Avec les transducteurs, vous l'écrirez comme:

(def xform
  (comp
    (map #(+ 2 %))
    (filter odd?)))
(transduce xform + (range 0 10))

Ils font tous de même. La différence est que vous n'appelez jamais directement les transducteurs, vous les passez à une autre fonction. Les transducteurs savent quoi faire, la fonction qui obtient le transducteur sait comment. L'ordre des combinateurs est comme vous l'écrivez avec une macro de thread (ordre naturel). Vous pouvez maintenant réutiliser xform avec le canal:

(chan 1 xform)
68
Aleš Roubíček

Les transducteurs améliorent l'efficacité et vous permettent d'écrire du code efficace de manière plus modulaire.

Ceci est un parcours décent .

Comparé à la composition d'appels à l'ancien map, filter, reduce etc., vous obtenez de meilleures performances car vous n'avez pas besoin de créer des collections intermédiaires entre chaque étape et de marcher à plusieurs reprises ces collections.

Comparé à reducers, ou en composant manuellement toutes vos opérations dans une seule expression, vous obtenez plus facilement des abstractions, une meilleure modularité et la réutilisation des fonctions de traitement.

41
noisesmith

Les transducteurs sont un moyen de combinaison pour réduire les fonctions.

Exemple: les fonctions de réduction sont des fonctions qui prennent deux arguments: un résultat jusqu'à présent et une entrée. Ils retournent un nouveau résultat (jusqu'à présent). Par exemple +: Avec deux arguments, vous pouvez considérer le premier comme le résultat jusqu'à présent et le second comme l'entrée.

Un transducteur peut désormais prendre la fonction + et en faire une fonction deux fois plus (double chaque entrée avant de l'ajouter). Voici à quoi ressemblerait ce transducteur (en termes les plus élémentaires):

(defn double
  [rfn]
  (fn [r i] 
    (rfn r (* 2 i))))

Pour l'illustration, remplacez rfn par + pour voir comment + est transformé en double plus:

(def twice-plus ;; result of (double +)
  (fn [r i] 
    (+ r (* 2 i))))

(twice-plus 1 2)  ;-> 5
(= (twice-plus 1 2) ((double +) 1 2)) ;-> true

Alors

(reduce (double +) 0 [1 2 3]) 

donnerait maintenant 12.

Les fonctions de réduction renvoyées par les transducteurs sont indépendantes de la façon dont le résultat est accumulé car elles s'accumulent avec la fonction de réduction qui leur est transmise, sans savoir comment. Ici, nous utilisons conj au lieu de +. Conj prend une collection et une valeur et retourne une nouvelle collection avec cette valeur ajoutée.

(reduce (double conj) [] [1 2 3]) 

donnerait [2 4 6]

Ils sont également indépendants du type de source d'entrée.

Plusieurs transducteurs peuvent être enchaînés comme une recette (chaînable) pour transformer les fonctions réductrices.

Mise à jour: Puisqu'il y a maintenant une page officielle à ce sujet, je recommande fortement de la lire: http://clojure.org/transducers

20
Leon Grapenthin

Supposons que vous souhaitiez utiliser une série de fonctions pour transformer un flux de données. Le shell Unix vous permet de faire ce genre de chose avec l'opérateur de tuyau, par ex.

cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l

(La commande ci-dessus compte le nombre d'utilisateurs avec la lettre r en majuscule ou en minuscule dans leur nom d'utilisateur). Ceci est implémenté comme un ensemble de processus, dont chacun lit à partir de la sortie des processus précédents, il y a donc quatre flux intermédiaires. Vous pourriez imaginer une implémentation différente qui compose les cinq commandes en une seule commande d'agrégation, qui lirait son entrée et écrirait sa sortie exactement une fois. Si les flux intermédiaires étaient chers et que la composition était bon marché, cela pourrait être un bon compromis.

Le même genre de chose vaut pour Clojure. Il existe plusieurs façons d'exprimer un pipeline de transformations, mais selon la façon dont vous le faites, vous pouvez vous retrouver avec des flux intermédiaires passant d'une fonction à la suivante. Si vous avez beaucoup de données, il est plus rapide de composer ces fonctions en une seule fonction. Les transducteurs facilitent cela. Une innovation antérieure de Clojure, les réducteurs, vous permet également de le faire, mais avec certaines restrictions. Les transducteurs suppriment certaines de ces restrictions.

Donc, pour répondre à votre question, les transducteurs ne rendront pas nécessairement votre code plus court ou plus compréhensible, mais votre code ne sera probablement pas plus long ou moins compréhensible non plus, et si vous travaillez avec beaucoup de données, les transducteurs peuvent rendre votre code plus rapide.

This est un assez bon aperçu des transducteurs.

19
user100464

Rich Hickey a prononcé une conférence sur les transducteurs lors de la conférence Strange Loop 2014 (45 min).

Il explique de manière simple ce que sont les transducteurs, avec des exemples concrets - le traitement des sacs dans un aéroport. Il sépare clairement les différents aspects et les contraste avec les approches actuelles. Vers la fin, il donne la justification de leur existence.

Vidéo: https://www.youtube.com/watch?v=6mTbuzafcII

10
Jordan Biserkov

J'ai trouvé des exemples de lecture de transducers-js m'aide à les comprendre en termes concrets de la façon dont je pourrais les utiliser dans le code au jour le jour.

Par exemple, considérons cet exemple (extrait de README sur le lien ci-dessus):

var t = require("transducers-js");

var map    = t.map,
    filter = t.filter,
    comp   = t.comp,
    into   = t.into;

var inc    = function(n) { return n + 1; };
var isEven = function(n) { return n % 2 == 0; };
var xf     = comp(map(inc), filter(isEven));

console.log(into([], xf, [0,1,2,3,4])); // [2,4]

D'une part, l'utilisation de xf semble beaucoup plus propre que l'alternative habituelle avec Underscore.

_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);
8
enocom

Les transducteurs sont (à ma connaissance!) Des fonctions qui prennent une fonction réduction et en retournent une autre. Une fonction réductrice est une fonction qui

Par exemple:

user> (def my-transducer (comp count filter))
#'user/my-transducer
user> (my-transducer even? [0 1 2 3 4 5 6])
4
user> (my-transducer #(< 3 %) [0 1 2 3 4 5 6])
3

Dans ce cas, my-transducer prend une fonction de filtrage d'entrée qui s'applique à 0, alors si cette valeur est paire? dans le premier cas, le filtre transmet cette valeur au compteur, puis il filtre la valeur suivante. Au lieu de filtrer d'abord, puis de passer toutes ces valeurs à compter.

C'est la même chose dans le deuxième exemple, il vérifie une valeur à la fois et si cette valeur est inférieure à 3, il laisse le compte ajouter 1.

7
Tobias

Une définition claire du transducteur est ici:

Transducers are a powerful and composable way to build algorithmic transformations that you can reuse in many contexts, and they’re coming to Clojure core and core.async.

Pour le comprendre, considérons l'exemple simple suivant:

;; The Families in the Village

(def village
  [{:home :north :family "smith" :name "sue" :age 37 :sex :f :role :parent}
   {:home :north :family "smith" :name "stan" :age 35 :sex :m :role :parent}
   {:home :north :family "smith" :name "simon" :age 7 :sex :m :role :child}
   {:home :north :family "smith" :name "sadie" :age 5 :sex :f :role :child}

   {:home :south :family "jones" :name "jill" :age 45 :sex :f :role :parent}
   {:home :south :family "jones" :name "jeff" :age 45 :sex :m :role :parent}
   {:home :south :family "jones" :name "jackie" :age 19 :sex :f :role :child}
   {:home :south :family "jones" :name "jason" :age 16 :sex :f :role :child}
   {:home :south :family "jones" :name "june" :age 14 :sex :f :role :child}

   {:home :west :family "brown" :name "billie" :age 55 :sex :f :role :parent}
   {:home :west :family "brown" :name "brian" :age 23 :sex :m :role :child}
   {:home :west :family "brown" :name "bettie" :age 29 :sex :f :role :child}

   {:home :east :family "williams" :name "walter" :age 23 :sex :m :role :parent}
   {:home :east :family "williams" :name "wanda" :age 3 :sex :f :role :child}])

Qu'en est-il nous voulons savoir combien d'enfants sont dans le village? Nous pouvons facilement le découvrir avec le réducteur suivant:

;; Example 1a - using a reducer to add up all the mapped values

(def ex1a-map-children-to-value-1 (r/map #(if (= :child (:role %)) 1 0)))

(r/reduce + 0 (ex1a-map-children-to-value-1 village))
;;=>
8

Voici une autre façon de procéder:

;; Example 1b - using a transducer to add up all the mapped values

;; create the transducers using the new arity for map that
;; takes just the function, no collection

(def ex1b-map-children-to-value-1 (map #(if (= :child (:role %)) 1 0)))

;; now use transduce (c.f r/reduce) with the transducer to get the answer 
(transduce ex1b-map-children-to-value-1 + 0 village)
;;=>
8

En outre, il est également très puissant lors de la prise en compte des sous-groupes. Par exemple, si nous aimerions savoir combien d'enfants sont dans la famille Brown, nous pouvons exécuter:

;; Example 2a - using a reducer to count the children in the Brown family

;; create the reducer to select members of the Brown family
(def ex2a-select-brown-family (r/filter #(= "brown" (string/lower-case (:family %)))))

;; compose a composite function to select the Brown family and map children to 1
(def ex2a-count-brown-family-children (comp ex1a-map-children-to-value-1 ex2a-select-brown-family))

;; reduce to add up all the Brown children
(r/reduce + 0 (ex2a-count-brown-family-children village))
;;=>
2

J'espère que vous trouverez ces exemples utiles. Vous pouvez trouver plus ici

J'espère que ça aide.

Clemencio Morales Lucas.

J'ai blogué à ce sujet avec un clojurescript exemple qui explique comment les fonctions de séquence sont maintenant extensibles en pouvant remplacer la fonction de réduction.

C'est le point des transducteurs comme je l'ai lu. Si vous pensez à l'opération cons ou conj qui est codée en dur dans des opérations comme map, filter etc., la fonction de réduction était inaccessible.

Avec les transducteurs, la fonction de réduction est découplée et je peux la remplacer comme je l'ai fait avec le tableau natif javascript Push grâce aux transducteurs.

(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-Push #js[]) #js [] nextKeys)

filter et amis ont une nouvelle opération 1 arity qui retournera une fonction de transduction que vous pouvez utiliser pour fournir votre propre fonction de réduction.

4
dagda1

Voici mon (principalement) jargon et réponse sans code.

Pensez aux données de deux manières, un flux (des valeurs qui surviennent dans le temps telles que des événements) ou une structure (des données qui existent à un moment donné comme une liste, un vecteur, un tableau, etc.).

Il existe certaines opérations que vous souhaiterez peut-être effectuer sur des flux ou des structures. L'une de ces opérations est la cartographie. Une fonction de mappage peut incrémenter chaque élément de données (en supposant qu'il s'agit d'un nombre) de 1 et vous pouvez, espérons-le, imaginer comment cela pourrait s'appliquer à un flux ou à une structure.

Une fonction de mappage n'est que l'une d'une classe de fonctions qui sont parfois appelées "fonctions réductrices". Une autre fonction de réduction courante est le filtre qui supprime les valeurs qui correspondent à un prédicat (par exemple, supprimez toutes les valeurs qui sont paires).

Les transducteurs vous permettent de "boucler" une séquence d'une ou plusieurs fonctions réductrices et de produire un "package" (qui est lui-même une fonction) qui fonctionne à la fois sur les flux ou les structures. Par exemple, vous pouvez "empaqueter" une séquence de fonctions réductrices (par exemple filtrer les nombres pairs, puis mapper les nombres résultants pour les incrémenter de 1), puis utiliser ce "paquet" de transducteur sur un flux ou une structure de valeurs (ou les deux) .

Alors, qu'est-ce qui est spécial à ce sujet? En règle générale, les fonctions réductrices ne peuvent pas être composées efficacement pour fonctionner à la fois sur les flux et les structures.

L'avantage pour vous est que vous pouvez tirer parti de vos connaissances sur ces fonctions et les appliquer à davantage de cas d'utilisation. Le coût pour vous est que vous devez apprendre des machines supplémentaires (c'est-à-dire le transducteur) pour vous donner cette puissance supplémentaire.

3
optevo

Pour autant que je comprends, ils sont comme blocs de construction, découplés de l'implémentation d'entrée et de sortie. Vous définissez simplement l'opération.

Comme l'implémentation de l'opération n'est pas dans le code de l'entrée et rien n'est fait avec la sortie, les transducteurs sont extrêmement réutilisables. Ils me rappellent Flow s dans Akka Streams .

Je suis également nouveau dans les transducteurs, désolé pour la réponse peut-être peu claire.

2
kadir malak

Je trouve que ce post vous donne une vue plus à vol d'oiseau du transducteur.

https://medium.com/@roman01la/understanding-transducers-in-javascript-3500d3bd9624

1
ka yu Lai