Redactar en todas partes: composici贸n de funciones en JavaScript

La traducci贸n del art铆culo ha sido preparada especialmente para los alumnos del curso JavaScript Developer.Basic .










Introducci贸n



Parece que las bibliotecas Lodash y Underscore ahora son ubicuas y todav铆a tienen el m茅todo de composici贸n s煤per eficiente que conocemos hoy .



Echemos un vistazo m谩s de cerca a la funci贸n de redacci贸n y veamos c贸mo puede hacer que su c贸digo sea m谩s legible, f谩cil de mantener y elegante.



Los basicos



Cubriremos muchas funciones de Lodash porque 1) no vamos a escribir nuestros propios algoritmos b谩sicos, esto nos distraer谩 de lo que sugiero enfocarnos; y 2) La biblioteca Lodash es utilizada por muchos desarrolladores y puede ser reemplazada por Underscore, cualquier otra biblioteca o sus propios algoritmos sin ning煤n problema.



Antes de ver algunos ejemplos simples, echemos un vistazo a lo que hace la funci贸n de composici贸n y veamos c贸mo implementar su propia funci贸n de composici贸n si es necesario.



var compose = function(f, g) {
    return function(x) {
        return f(g(x));
    };
};


Esta es la implementaci贸n m谩s b谩sica. Tenga en cuenta que las funciones en los argumentos se ejecutar谩n de derecha a izquierda . Es decir, la funci贸n de la derecha se ejecuta primero y su resultado se pasa a la funci贸n de la izquierda.



Ahora veamos este c贸digo:



function reverseAndUpper(str) {
  var reversed = reverse(str);
  return upperCase(reversed);
}


La funci贸n reverseAndUpper primero invierte la cadena especificada y luego la convierte a may煤sculas. Podemos reescribir este c贸digo usando la funci贸n de composici贸n b谩sica:



var reverseAndUpper = compose(upperCase, reverse);


La funci贸n reverseAndUpper ahora se puede utilizar:



reverseAndUpper(''); // 


Podr铆amos haber obtenido el mismo resultado escribiendo el c贸digo:



function reverseAndUpper(str) {
  return upperCase(reverse(str));
}


Esta opci贸n se ve m谩s elegante y es m谩s f谩cil de mantener y reutilizar.



La capacidad de dise帽ar funciones r谩pidamente y crear canales de procesamiento de datos ser谩 煤til en diversas situaciones en las que necesite transformar datos sobre la marcha. Ahora imagine que necesita pasar una colecci贸n a una funci贸n, transformar los elementos de la colecci贸n y devolver el valor m谩ximo despu茅s de que se hayan completado todos los pasos de la canalizaci贸n, o convertir una cadena dada en un valor booleano. La funci贸n de redacci贸n le permite combinar m煤ltiples funciones y as铆 crear una funci贸n m谩s compleja.



Implementemos una funci贸n de composici贸n m谩s flexible que pueda incluir cualquier n煤mero de otras funciones y argumentos. La funci贸n de composici贸n anterior que vimos solo funciona con dos funciones y solo toma el primer argumento pasado. Podemos reescribirlo as铆:



var compose = function() {
  var funcs = Array.prototype.slice.call();
 
  return funcs.reduce(function(f,g) {
    return function() {
      return f(g.apply(this, ));
    };
  });
};


Con una funci贸n como esta, podemos escribir c贸digo como este:



Var doSometing = compose(upperCase, reverse, doSomethingInitial);
 
doSomething('foo', 'bar');


Hay muchas bibliotecas que implementan la funci贸n de redacci贸n. Necesitamos nuestra funci贸n de redacci贸n para comprender c贸mo funciona. Por supuesto, se implementa de manera diferente en diferentes bibliotecas. Las funciones de composici贸n de diferentes bibliotecas hacen lo mismo, por lo que son intercambiables. Ahora que entendemos de qu茅 se trata la funci贸n de composici贸n, utilicemos los siguientes ejemplos para ver la funci贸n _.compose de la biblioteca Lodash.



Ejemplos de



Comencemos simple:



function notEmpty(str) {
    return ! _.isEmpty(str);
}


La funci贸n notEmpty es la negaci贸n del valor devuelto por la funci贸n _.isEmpty.



Podemos lograr el mismo resultado usando la funci贸n _.compose de la biblioteca Lodash. Escribamos la funci贸n no:



function not(x) { return !x; }
 
var notEmpty = _.compose(not, _.isEmpty);


Ahora puede usar la funci贸n notEmpty con cualquier argumento:



notEmpty('foo'); // true
notEmpty(''); // false
notEmpty(); // false
notEmpty(null); // false


Este es un ejemplo muy simple. Echemos un vistazo a algo

un poco m谩s complicado: la funci贸n findMaxForCollection devuelve el valor m谩ximo de una colecci贸n de objetos con propiedades id y val (valor).



function findMaxForCollection(data) {
    var items = _.pluck(data, 'val');
    return Math.max.apply(null, items);
}
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data);


La funci贸n de redacci贸n se puede utilizar para resolver este problema:



var findMaxForCollection = _.compose(function(xs) { return Math.max.apply(null, xs); }, _.pluck);
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data, 'val'); // 6


Aqu铆 hay trabajo por hacer.



_.pluck espera una colecci贸n como primer argumento y una funci贸n de devoluci贸n de llamada como segundo. 驴Es posible aplicar parcialmente la funci贸n _.pluck? En este caso, puede utilizar currying para cambiar el orden de los argumentos.



function pluck(key) {
    return function(collection) {
        return _.pluck(collection, key);
    }
}


La funci贸n findMaxForCollection necesita modificarse un poco m谩s. Creemos nuestra propia funci贸n m谩xima.



function max(xs) {
    return Math.max.apply(null, xs);
}


Ahora podemos hacer que nuestra funci贸n de redacci贸n sea m谩s elegante:



var findMaxForCollection = _.compose(max, pluck('val'));
 
findMaxForCollection(data);


Escribimos nuestra propia funci贸n de extracci贸n y solo podemos usarla con la propiedad 'val'. Es posible que no comprenda por qu茅 deber铆a escribir su propio m茅todo de b煤squeda si Lodash ya tiene una funci贸n conveniente y lista para usar _.pluck. El problema es que _.pluckespera una colecci贸n como primer argumento y queremos hacerlo de manera diferente. Al cambiar el orden de los argumentos, podemos aplicar parcialmente la funci贸n pasando la clave como primer argumento; la funci贸n devuelta aceptar谩 datos.

Podemos refinar un poco m谩s nuestro m茅todo de muestreo. Lodash tiene un m茅todo 煤til _.curryque te permite escribir nuestra funci贸n as铆:



function plucked(key, collection) {
    return _.pluck(collection, key);
}
 
var pluck = _.curry(plucked);


Simplemente ajustamos la funci贸n de extracci贸n original para intercambiar los argumentos. Ahora, pluck devolver谩 la funci贸n hasta que haya procesado todos los argumentos. Veamos c贸mo se ve el c贸digo final:



function max(xs) {
    return Math.max.apply(null, xs);
}
 
function plucked(key, collection) {
    return _.pluck(collection, key);
}
 
var pluck = _.curry(plucked);
 
var findMaxForCollection = _.compose(max, pluck('val'));
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data); // 6


La funci贸n findMaxForCollection se puede leer de derecha a izquierda, es decir, primero la propiedad val de cada elemento de la colecci贸n se pasa a la funci贸n y luego devuelve el m谩ximo de todos los valores aceptados.



var findMaxForCollection = _.compose(max, pluck('val'));


Este c贸digo es m谩s f谩cil de mantener, reutilizable y mucho m谩s elegante. Veamos el 煤ltimo ejemplo, solo para disfrutar de la elegancia de la composici贸n funcional una vez m谩s.



Tomemos los datos del ejemplo anterior y agreguemos una nueva propiedad llamada active. Ahora los datos se ven as铆:



var data = [{id: 1, val: 5, active: true}, 
            {id: 2, val: 6, active: false }, 
            {id: 3, val: 2, active: true }];




Llamemos a esta funci贸n getMaxIdForActiveItems (data) . Toma una colecci贸n de objetos, filtra todos los objetos activos y devuelve el valor m谩ximo de los filtrados.



function getMaxIdForActiveItems(data) {
    var filtered = _.filter(data, function(item) {
        return item.active === true;
    });
 
    var items = _.pluck(filtered, 'val');
    return Math.max.apply(null, items);
}


驴Puedes hacer que este c贸digo sea m谩s elegante? Ya tiene las funciones max y pluck, por lo que solo necesitamos agregar un filtro:



var getMaxIdForActiveItems = _.compose(max, pluck('val'), _.filter);
 
getMaxIdForActiveItems(data, function(item) {return item.active === true; }); // 5


_.filterEl mismo problema surge con la funci贸n que con _.pluck: no podemos aplicar parcialmente esta funci贸n, porque espera una colecci贸n como primer argumento. Podemos cambiar el orden de los argumentos en el filtro envolviendo la funci贸n inicial:



function filter(fn) {
    return function(arr) {
        return arr.filter(fn);
    };
}


Agreguemos una funci贸n isActiveque toma un objeto y verifica si la bandera activa est谩 configurada como verdadera.



function isActive(item) {
    return item.active === true;
}


Una funci贸n filtercon una funci贸n isActivese puede aplicar parcialmente, por lo que solo pasaremos datos a la funci贸n getMaxIdForActiveItems .



var getMaxIdForActiveItems = _.compose(max, pluck('val'), filter(isActive));


Ahora solo necesitamos transferir datos:



getMaxIdForActiveItems(data); // 5


Ahora nada nos impide reescribir esta funci贸n para que encuentre el valor m谩ximo entre los objetos inactivos y lo devuelva:



var isNotActive = _.compose(not, isActive);

var getMaxIdForNonActiveItems = _.compose(max, pluck('val'), filter(isNotActive));


Conclusi贸n



Como habr谩 notado, la composici贸n de funciones es una caracter铆stica interesante que hace que su c贸digo sea elegante y reutilizable. Lo principal es aplicarlo correctamente.



Enlaces



lodash 隆Oye, subrayado

, lo est谩s haciendo mal! (隆Oye, subrayado, lo est谩s haciendo todo mal!)

@sharifsbeat







Lee mas:






All Articles