¿Puede alguien explicar Clojure Transducers en términos simples?



Answers

Los transductores mejoran la eficiencia y le permiten escribir códigos eficientes de una manera más modular.

Esta es una carrera decente .

En comparación con la composición de llamadas al antiguo map , filter , reduce , etc. obtiene un mejor rendimiento porque no necesita crear colecciones intermedias entre cada paso, y camina repetidamente por esas colecciones.

Comparado con los reducers , o componiendo manualmente todas tus operaciones en una sola expresión, obtienes más abstracciones, mejor modularidad y reutilización de las funciones de procesamiento.

Question

He intentado leer sobre esto pero todavía no entiendo su valor o lo que reemplazan. ¿Y hacen que mi código sea más corto, más comprensible o qué?

Actualizar

Muchas personas publicaron respuestas, pero sería bueno ver ejemplos de con y sin transductores para algo muy simple, que incluso un idiota como yo puede entender. A menos que, por supuesto, los transductores necesiten un alto nivel de comprensión, en cuyo caso nunca los entenderé :(




He encontrado que leer ejemplos de transducers-js me ayuda a entenderlos en términos concretos de cómo podría usarlos en el código diario.

Por ejemplo, considere este ejemplo (tomado del archivo README en el enlace de arriba):

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]

Por un lado, usar xf parece mucho más limpio que la alternativa habitual con Underscore.

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



Una clara definición de transductor está aquí:

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.

Para entenderlo, consideremos el siguiente ejemplo simple:

;; 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é tal si queremos saber cuántos niños hay en el pueblo? Podemos encontrarlo fácilmente con el siguiente reductor:

;; 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

Aquí hay otra manera de hacerlo:

;; 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

Además, es realmente poderoso cuando se toman subgrupos en la cuenta también. Por ejemplo, si quisiéramos saber cuántos niños hay en la familia Brown, podemos ejecutar:

;; 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

Espero que puedas encontrar útiles estos ejemplos. Puedes encontrar más here

Espero eso ayude.

Clemencio Morales Lucas.




Aquí está mi (sobre todo) jerga y código de respuesta gratuita.

Piense en los datos de dos maneras, una secuencia (valores que ocurren a lo largo del tiempo, como los eventos) o una estructura (datos que existen en un momento dado, como una lista, un vector, una matriz, etc.).

Hay ciertas operaciones que puede desear realizar sobre secuencias o estructuras. Una de esas operaciones es mapeo. Una función de mapeo puede incrementar cada elemento de datos (asumiendo que es un número) por 1 y con suerte puede imaginar cómo esto podría aplicarse a una secuencia o una estructura.

Una función de mapeo es solo una de una clase de funciones que a veces se denominan "funciones reductoras". Otra función de reducción común es el filtro que elimina valores que coinciden con un predicado (por ejemplo, eliminar todos los valores que son pares).

Los transductores le permiten "ajustar" una secuencia de una o más funciones reductoras y producir un "paquete" (que a su vez es una función) que funciona tanto en secuencias como en estructuras. Por ejemplo, podría "empaquetar" una secuencia de funciones reductoras (por ejemplo, filtrar números pares, luego asignar los números resultantes para incrementarlos en 1) y luego usar ese "paquete" de transductores en una secuencia o estructura de valores (o ambos) .

Entonces, ¿qué tiene de especial esto? Por lo general, la reducción de funciones no puede ser compuesta eficientemente para trabajar tanto en las secuencias como en las estructuras.

Entonces, el beneficio para usted es que puede aprovechar su conocimiento sobre estas funciones y aplicarlas a más casos de uso. El costo para usted es que tiene que aprender un poco de maquinaria adicional (es decir, el transductor) para darle este poder extra.




Supongamos que quiere usar una serie de funciones para transformar una secuencia de datos. El shell Unix le permite hacer este tipo de cosas con el operador de tubería, por ejemplo

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

(El comando anterior cuenta el número de usuarios con la letra r en mayúscula o minúscula en su nombre de usuario). Esto se implementa como un conjunto de procesos, cada uno de los cuales se lee a partir de los resultados de los procesos anteriores, por lo que hay cuatro flujos intermedios. Se podría imaginar una implementación diferente que componga los cinco comandos en un solo comando agregado, que leería de su entrada y escribiría su salida exactamente una vez. Si las transmisiones intermedias son caras y la composición es barata, podría ser una buena solución de compromiso.

El mismo tipo de cosas son válidas para Clojure. Hay formas múltiples y concisas de expresar una serie de transformaciones, pero dependiendo de cómo lo haga, puede terminar con flujos intermedios que pasan de una función a la siguiente. Si tiene muchos datos, es más rápido componer esas funciones en una sola función. Los transductores hacen que sea más fácil hacer eso. Una innovación anterior de Clojure, reductores, le permite hacer eso también, pero con algunas restricciones. Los transductores eliminan algunas de esas restricciones.

Entonces, para responder a su pregunta, los transductores no necesariamente harán que su código sea más corto o más comprensible, pero su código probablemente tampoco será más extenso o menos comprensible, y si está trabajando con una gran cantidad de datos, los transductores pueden hacer su código Más rápido.

This es una muy buena descripción general de los transductores.




Por lo que yo entiendo, son como bloques de construcción , desacoplados de la implementación de entrada y salida. Usted solo define la operación.

Como la implementación de la operación no está en el código de entrada y no se hace nada con la salida, los transductores son extremadamente reutilizables. Me recuerdan a Flow s en Akka Streams .

También soy nuevo en los transductores, lo siento por la respuesta posiblemente confusa.




Related