Programación Funcional en JavaScript: Los métodos funcionales

map-filter-reduce-in-emoji-1.png

Creo que Swift explica los métodos funcionales mejor que yo. Si, el anterior código compilaría en Swift.

Después de terminar con la primera fase introductoria de esta serie, nos adentramos en una nueva fase dedicada a explicar cómo va a ser el control de flujo en una aplicación de estilo funcional. Esta segunda serie va a constar de dos posts: uno dedicado a  lo que conocemos como métodos funcionales y otro para explicar el término de recursividad.

En el actual post, nos centraremos en contestar a las siguientes preguntas: ¿Qué es un control de flujo? ¿Qué es el encadenamiento de métodos y funciones? y ¿Qué son las expresiones lambda?

Por último, os enseñaré a usar tres métodos funcionales muy conocidos en JavaScript y en programación funcional como son map, reduce y filter.

Seguimos haciendo uso del libro ‘Functional Programming in javaScript: How to improve yout JavaScript programs using functional techniques‘. Espero que os guste. Adelante:

¿Qué entendemos por control de flujo en una aplicación?

Nos referimos con control de flujo en una aplicación a los posibles caminos que se pueden llegar a tener para llegar a una solución determinada.

En programación imperativa estamos muy acostumbrados a indicar a nuestros programas un mapa exhaustivo de todos los caminos posible en los que puede desembocar un problema determinado.

Un ejemplo muy típico en programación imperativa podría ser el siguiente:

var loop = optC();
while (loop) {
    var condition = optA();
    if (condition) {
       optB1();
    } else {
       optB2();
    }
    loop = optC();
}
optD();

Como vemos, las iteraciones y las ramificaciones son importantes a la hora de entender el estilo imperativo. El estilo funcional apuesta por separar el control de flujo de una aplicación por medio de abstracciones que se comportan como cajas negras que lo único que hacen es traspasar estados de una operación a otra.

Por lo tanto en un estilo funcional, nuestro mayor objetivo, será conseguir un código parecido al siguiente:

optA().optB().optC().optD();

Un estilo mucho más expresivo donde solo se indique que hacemos con una abstracción de alto nivel.

¿Qué son los métodos encadenados?

Los métodos encadenados son un patrón de diseño muy usado en lenguajes orientados a objetos que vienen muy bien a la hora de crear APIs estilo ‘fluent’. La idea es hacer operaciones sobre el estado de un objeto y que estos métodos devuelven el mismo objeto manipulado de tal forma que cuando hagamos uso de los métodos podamos encadenarlos unos de detrás de otros.

Encontramos un caso representativo en el objeto String de JavaScript:

'Hello World'.toSubString(0, 10).toLowerCase();

El código es muy parecido a lo que queremos conseguir con el estilo funcional. Además, en este caso no tenemos los problemas imperativos con la mutabilidad de los estados ya que en JavaScript el tipo String es inmutable por lo que cuando realizamos un cambio en el string de origen, nosotros obtenemos una cadena totalmente nueva.

Esto no ocurrirá con todos los tipos u objetos por lo que, por ahora nos quedaremos más con la idea conceptual del patrón que con la propia implementación.

¿Qué son las funciones encadenadas?

Siguiendo con la comparación imperativa, nosotros sabemos que una de las formas de conseguir código y comportamiento reutilizable en orientación a objetos es por medio de la herencia. Yo tengo una clase tipo ‘Persona’ y puedo hacer que sus atributos y comportamiento sean heredados por una nueva estructura denominada ‘Cliente’.

Nos ocurre incluso para crear nuevas estructuras de datos. En el propio Java existen estructuras de datos que heredan de ‘List’ como ‘ArrayList’ o ‘LinkedList’ que tienen un comportamiento específico según la necesidad.

En el paradigma declarativo no se opta por la herencia de estructuras según necesidades comunes. En vez de ello se opta por aplicar funciones de orden superior a arrays para obtener el comportamiento deseado. El poder encadenar operaciones sobre diferentes elementos de esta manera nos ayuda en dos aspectos:

  • Evitar los tradicionales mecanismos de bucle para iterar sobre elementos.
  • Poder inyectar comportamiento especializado para realizar la tarea que precisamos.

Pongamos un ejemplo: Imaginemos que necesitamos multiplicar por 2 todos los elementos pares de un array. En estilo imperativo esto sería de esta forma;

const array = [1, 2, 3, 4, 5, 6];
const arrayFinal = [array.length];

for (let i = 0; i < array.length; i++) {
    arrayFinal[i] = (array[i] % 2 === 0) ? array[i] * 2 : array[i];
}

console.log(arrayFinal); // [1, 4, 3, 8, 5, 12]

Imaginemos ahora que, de pronto, nos ha salido otra necesidad sobre los array en que tenemos que multiplique por 3 a todos los elementos impares. La función sería la siguiente:

const array = [1, 2, 3, 4, 5, 6];
const arrayFinal = [array.length];

for (let i = 0; i < array.length; i++) {
    arrayFinal[i] = (array[i] % 2 === 1) ? array[i] * 3 : array[i];
}

console.log(arrayFinal); // [4, 2, 6, 4, 8, 6]

Como vemos, demasiado código repetido (He señalado en negrita las diferencias). Tenemos que refactorizarlo. Si lo hiciésemos con orientado a objetos podríamos crear un tipo base que recorriera el array y un método privado encargado de la especialización. Después con dos clases que heredasen de la base y sobreescribieran el método, lo tendríamos. Es el patrón Template Method de calle.

En funcional vamos a hacer algo parecido. Como podemos pasar funciones como parámetros, vamos a abstraer a una función todo lo común:

function loop(array, func)  {
    const arrayFinal = [array.length];

    for (let i = 0; i < array.length; i++) {
        arrayFinal[i] = func(array[i]);
    }

    return arrayFinal;
}

Si ahora queremos realizar los dos casos de uso, lo haríamos así:

const array = [1, 2, 3, 4, 5, 6];
const arrayFinal2 = loop(array, element => (element % 2 === 0) ? element * 2 : element);
const arrayFinal3 = loop(array, element => (element % 2 === 1) ? element * 3 : element);

console.log(arrayFinal2); // [1, 4, 3, 8, 5, 12]
console.log(arrayFinal3); // [3, 2, 9, 4, 15, 6]

Incluso en este ejemplo, tenemos algo de código repetido. Podemos hacerlo mejor:

const array = [1, 2, 3, 4, 5, 6];
const getExpression = function (rest, multiplier) {
    return (element => element % 2 === rest ? element * multiplier : element);
};
const arrayFinal2 = loop(array, getExpression(0, 2));
const arrayFinal3 = loop(array, getExpression(1, 3));

console.log(arrayFinal2); // [1, 4, 3, 8, 5, 12]
console.log(arrayFinal3); // [3, 2, 9, 4, 15, 6]

Encadenar la ejecución de una función-parámetro nos va a permitir conseguir este funcionamiento. Lo veremos en los ejemplos de ‘map’, ‘reduce’ y ‘filter’.

¿Qué son las expresiones lambda?

Son lo que también llamamos en JavaScript como ‘fat arrow’. Son funciones anónimas que siempre devuelven un valor. En JavaScript existen desde ES6.

Las expresiones lambda pueden contener más de una sentencia, pero yo recomiendo que se usen como expresiones simple de una única sentencia.El objetivo de las expresiones en sistemas funcionales es la de realizar una acción específica sobre unos parámetros dados, por lo que si necesitamos realizar varias acciones sería mejor usar una función normal.

Además de que tener varias sentencias es algo más típico del modelo imperativo. Evitemos las sentencias y promovamos el uso de expresiones.

En JavaScript las expresiones lambda no son más que azúcar sintáctico. Simplifican la forma de escribir una función con menos elementos. Si antiguamente teníamos este elemento:

const getFullName = function (obj) { return obj.fullname; }

Ahora contamos con una sintaxis más sencilla:

const getFullName = obj => obj.fullname;

Que hace que nos centramos en la expresión y no tanto en escribir código.

‘map’, ‘reduce’ y ‘filter’ en JavaScript

Todo lo aprendido en este post, nos ayuda a entender estos tres métodos. Son tres métodos incluidos en ES5 para manipular arrays que se basan en el encadenamiento de métodos y funciones y en el uso de expresiones lambda.

Son funciones muy conocidas en el mundo funcional pues nos ayudan a obtener datos con las transformaciones que nosotros necesitemos. En JavaScript existen librerías que tienen implementados estos métodos funcionales antes que el propio JavaScript que son Lodash y Underscore.

Las cito porque pueda ser que en futuros post tengamos que echar mano de ellas porque JavaScript no cuente con alguna implementación interesante. Por si queréis ir mirando su funcionalidad, ahí las tenéis 😉

Expliquemos el funcionamiento de nuestros 3 nuevos amigos:

Transformando datos con ‘map’

Al igual que hice en el ejemplo anterior, en muchas ocasiones vamos a tener la necesidad de ir recorriendo un array de elementos e ir transformando sus datos por medio de una función.

La función ‘map’ recibe una expresión como parámetro que realiza la transformación en particular que necesitemos. El dibujo siguiente explica muy bien el funcionamiento interno de ‘map’:

mapping

Dado un array y una expresión de transformación devuelve un nuevo array con la transformación ejecutada. Un ejemplo sería el siguiente:

[1, 2, 3, 4, 5, 6].map(x => x * 2); // [2, 4, 6, 8, 10, 12]

Sin bucles, sin efectos colaterales, sin mutaciones… una maravilla funcional :).

Reuniendo resultados con ‘reduce’

‘reduce’ se usa como acumulador de resultados. ‘reduce’ espera una expresión que cuente con un parámetro acumulador y el elemento en el que se encuentra el iterador. Lo que devuelve ‘reduce’ es el valor acumulado. Se comporta ‘parecido’ un ‘groupBy’ de SQL.

Un ejemplo muy simple sería el de obtener el total de un array:

[1, 2, 4, 5, 6].reduce((acumulado, elemento) => acumulado + elemento); // 18

Eliminando datos no deseados con ‘filter’

‘filter’ espera una expresión que indique si el elemento en el que me encuentro es deseado o no. Nos viene muy bien para hacer búsquedas en un array. Se comporta parecido a un ‘where’ de SQL.

[1, 2, 3, 4, 5, 6].filter(x => x % 2 === 0); // [2, 4, 6]

Pongamos todo junto

Y la potencia que tiene esto es brutal a la hora de manipular arrays ya que cumple también con el patrón de método encadenado.

Imaginemos que quiero sumar solo los números pares de un array donde los números me vienen en tipo string. Podríamos hacerlo de esta manera:

['1', '2', '3', '4', '5', '6']
    .map(parseFloat)
    .filter(x => x % 2 === 0)
    .reduce((x, y) => x + y); // 12

Yo que venía de picotear en LinQ fue como que se me iluminara el mundo de nuevo (LinQ es una API pensada para similar SQL en C# con un enfoque muy funcional).

Conclusión

Como hemos visto, métodos como filter, map y reduce nos hacen la vida más fácil en nuestros desarrollos funcionales. El poder evitar los bucles en nuestro código, conseguirá que cometamos menos errores. Los métodos funcionales además son una buena forma de no echar de menos al ámbito de bloque que explicamos en el post anterior.

En el próximo capítulo explicaremos otra forma distinta de controlar el flujo que tiene nuestra aplicación por medio de las técnicas recursivas.

Por el momento, es todo. Nos leemos 🙂

PD: Os dejo el mapa mental del post:

los-metodos-funcionales

Anteriores posts de Programación Funcional en JavaScript:

Introducción

  1. La Programación Funcional en JavaScript
  2. Programación Funcional en JavaScript: Los Objetos
  3. Programación Funcional en JavaScript: Las funciones

El control de flujo funcional

  1. La programación Funcional en JavaScript: Los métodos funcionales
  2. La programación Funcional en JavaScript: La recursividad

Imagen de portada | globalnerdy

Anuncios

15 comments

  1. Pingback: Programación Funcional en JavaScript: La recursividad | el.abismo = de[null]
  2. Pingback: Programación Funcional en JavaScript: La aridad y las tuplas | el.abismo = de[null]
  3. Pingback: Programación Funcional en JavaScript: La currificación | el.abismo = de[null]
  4. Pingback: Programación Funcional en JavaScript: La composición | el.abismo = de[null]
  5. Pingback: Programación Funcional en JavaScript: Los combinadores | el.abismo = de[null]
  6. Pingback: Programación Funcional en JavaScript: Los funtores | el.abismo = de[null]
  7. Pingback: Programación Funcional en JavaScript: La mónada Maybe | el.abismo = de[null]
  8. Pingback: Programación Funcional en JavaScript: La mónada Either | el.abismo = de[null]
  9. Pingback: Programación Funcional en JavaScript: La mónada IO | el.abismo = de[null]
  10. Pingback: Programación Funcional en JavaScript: La evaluación perezosa | el.abismo = de[null]
  11. Pingback: Programación Funcional en JavaScript: La memoización | el.abismo = de[null]
  12. Borja Guzman del Rio · febrero 22

    Cuando hablas de las fat arrow functions te refieres a ellas como azúcar sintáctico. Sin embargo, cuentan con una útil y sutil ventaja, el disponer de un contexto léxico. Esto es, el this no es evaluado en la fase de ejecución, si no en la fase de declaración. De este modo y teniendo:
    const foo= {
    bar: function(){ console.log(this) }
    };
    const bar = {
    foo: () => { console.log(this) }
    };
    Y ejecutando ambas funciones lo que obtendrás será:
    foo.bar() //Object foo{}
    bar.foo() //Object window

    Puede parecer que no es algo completamente significativo, pero a la hora pasar funciones como parámetros, callbacks o resolver de Promesas pueden resultar completamente definitivo haberlo declarado de una forma u otra.

    Le gusta a 1 persona

    • jdonsan · febrero 22

      Hola Borja,

      Gracias por escribir, se agradece mucho tu comentario.

      Si veo útiles, en ciertos momento, el uso de las arrow function y tienes razón que el ‘bindeo’ de ‘this’ es una buena solución para gestionar el contexto. algo que nos ha traído siempre problemas en JS.

      Lo que me refiero con que las arrow function son azúcar sintáctico es que no han solucionado nada que ya no pudiéramos hacer antes. Al final las arrow function son una manera implícita de hacer un ‘bind’. Por ejemplo, sobre tu ejemplo anterior yo podría tener esto:

      const foo = {
      bar: function(){ console.log(this) }
      };
      const bar = {
      foo: function(){ console.log(this) }.bind(this)
      };

      Consiguiendo el mismo resultado y sin tener que usar arrow function:
      foo.bar() //Object foo{}
      bar.foo() //Object window

      Perdona que quizá haya quedado como una crítica, pero creo que no han apartado tanto como muchas veces nos hacen pensar.

      Me gusta

  13. Hendrix Roa · abril 21

    Me hiciste el dia con la imagen de los emojis 🙂 Excelente post

    Le gusta a 1 persona

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s