Programación Funcional en JavaScript: La composición

2909172074_abdfb3a8b1_b.jpg

Si hemos visto todas estas técnicas sobre cómo crear funciones unarias, flexibles y versátiles es con la idea de que podamos componer funcionalidad más compleja a partir de estas funciones.

La composición es una de las claves de la programación funcional. Este post será un poco la piedra angular de esta serie pues todo lo aprendido hasta ahora desemboca en esta técnica de desarrollo. Hemos conseguidos separar todas las evaluaciones del código en mini funciones para que ahora podamos componer como el que junta un puzzle.

En este post explicaremos, con un ejemplo visual, cómo la composición funcional se parece mucho a la composición de elementos en HTML. Explicaremos a nivel formal la composición funcional e implementaremos una función que nos permita crear esta composición de una forma cómoda.

Mostraremos como RambdaJS ya tiene implementadas un montón de funciones útiles para la composición de módulos o programas. Por último, hablaremos sobre cómo nuestros programas funcionales deberán convivir con código puro e impuro:

Entendiendo la composición por medio de HTML

La composición se comporta de igual manera que nuestro HTML. Nosotros en HTML intentamos crear interfaces complejas por medio de la composición de partes más simples.

Imaginemos que tenemos que maquetar un formulario. Es el típico formulario de ‘onboarding’ de un usuario que cuenta con muchos campos a rellenar. Nuestra primera misión, como maquetadores, es la de observar cuales son los diferentes campos con los que cuenta este formulario. Cada campo de un formulario se compone de una ‘label’ y de un ‘input’. Estos primeros campos se asemejan mucho a las funciones puras con una única responsabilidad que hemos ido definiendo a la largo de la serie.

Lo siguiente que solemos hacer es agrupar los campos que más sentido tienen juntos para que el usuario navegue de una forma usable por el formulario. Por ejemplo, componemos una parte del formulario para obtener la información sobre datos personales o lo componemos para obtener datos de facturación. Nosotros en funcional, también intentamos agrupar nuestras funciones para crear módulos, partes reutilizables que dadas unas entradas devuelvan unas salidas determinadas.

Por último, lo que hacemos es coger todas estas partes y componerlas en el formulario final. O como hacemos en funcional, con estos módulos componemos aplicaciones más complejas.

 

Composición funcional

Separar la descripción de la evaluación es otra de las claves de la programación funcional. Si nos damos cuenta, es lo que llevamos haciendo durante toda la serie: Encontrar mecanismos que nos permitan esta separación.

Una vez que hemos conseguido construir funciones puras, con un único propósito y unarias, lo único que nos hace falta es poder usarlas para desarrollar cosas más complejas. La composición es el arte de dividir un problema complejo en pequeñas unidades funcionales que puedan juntarse como si de un puzzle se tratase.

La composición se basa en, que si yo tengo una función f que transforma el tipo de entrada A en el tipo de salida B y una función g que transforma el tipo de entrada B en el tipo de salida C, yo pueda obtener una tercera función que por la composición de f y g, pueda transformar tipos de entrada A en tipos de salida C.

Esto de manera formal se anota de esta manera:

f · g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)

Donde f · g se lee como ‘f compuesto de g’.

Lo importante de la composición es que nosotros sigamos respetando la transparencia referencial que se da en las funciones puras involucradas. Es decir, que si yo ejecuto una función f con un valor A que devuelve siempre un valor B, y después ejecuto una función g con ese valor B que siempre devuelve el valor C, que cuando yo use la función compuesta f(g), si yo incluyo un valor A siempre obtenga el valor C. Que consigamos un mapeo exacto en el conjunto entradas y salidas de ambas soluciones.

Con la siguiente imagen se explica mucho mejor mi trabalenguas 🙂

example_for_a_composition_of_two_functions-svg

Pongamos un ejemplo: Dada una cadena de texto, consigamos un programa que nos diga el número de palabras que tiene. Lo primero que vamos a hacer son las funciones. Estas son las siguientes:

const text = `En un lugar de la Mancha, de cuyo nombre no quiero 
acordarme, no ha mucho tiempo que vivía un hidalgo de 
los de lanza en astillero, adarga antigua, rocín flaco y 
galgo corredor. Una olla de algo más vaca que carnero, 
salpicón las más noches, duelos y quebrantos los sábados, 
lentejas los viernes, algún palomino de añadidura los domingos, 
consumían las tres partes de su hacienda.`;

// explode :: string -> array
const explode = txt => txt.split(' ');

// count :: array -> number
const count = arr => arr.length;

Ya podríamos hacer uso de estas funciones, concatenando la invocación de ambas de esta manera:

count(explode(text));

Sin embargo, nosotros no estamos hablando de esto. Estamos hablando de, por medio de funciones puras, componer y obtener otras nuevas funciones. Necesitamos algo que nos permita hacer esto:

// countWords :: string -> number
const countWords = compose(count, explode);

countWords(text);

Como hemos visto en varios momentos de esta serie, la funcionalidad ‘compose’ tampoco viene implementada por defecto en JavaScript. La solución viene por definir una función que se encargue de realizar este cometido. Una posibilidad es la propuesta por Luis Atencio en su libro:

1.   function compose() {
2.       const args = arguments;
3.       const start = args.length - 1;
4.       
5.       return function () {
6.           let i = start;
7.           let result = args[start].apply(this, arguments);
8.           while (i--) {
9.                result = args[i].call(this, result);
10.          }  
11.          return result; 
12.      }
13.  }

Es una función pequeña, pero con mucha miga. Vayamos por partes:

  • Lo primero que hace es guardarse la colección de funciones que hemos indicado de entrada en ‘args’ (línea 2) e instanciamos un contador (línea 3).
  • Lo siguiente que hacemos es devolver una función (línea 5). De esta manera volvemos a hacer uso del ámbito léxico (closure) para guardar este estado en sucesivas llamadas. De esta manera la nueva función que hemos devuelto recuerda su propio estado. Con este código hemos conseguido lo siguiente:
const countWords = compose(count, explode);
  • Internamente, ‘countWords’ está guardando un array con ‘count’ y ‘explode’ en ‘args’.
  • Cuando nosotros hacemos uso de ‘countWords’, estamos ejecutando un bucle que hace que se invoquen todas las funciones en orden descendente (línea 9). De esta manera las funciones se ejecutan en la tubería de derecha a izquierda.
  • Por último, devolvemos el valor definitivo (línea 11).

Por suerte, contamos con librerías como RambdaJS que ya cuentan con esta funcionalidad implementada. Nosotros podríamos hacer esto también:

// countWords :: string -> number
const countWords = R.compose(count, explode);

countWords(text);

Puede que no nos guste mucho esta forma de escribir código y que prefiramos otro estilo. Puede que prefiramos usar métodos encadenados. Para conseguir esto, podríamos hacer lo siguiente:

Function.prototype.compose = R.compose;

De esta manera el código anterior se podría reescribir de esta manera:

const countWords = count.compose(explode);

Y obtendríamos el mismo resultado.

Usando RambdaJS para componer funciones

RambdaJS cuenta con un gran abanico de funciones puras muy útiles para desarrollar módulos funcionales. Para ver el potencial de RambdaJS, vamos a realizar un pequeño ejemplo:

Tenemos que desarrollar un programa que sea capaz de obtener el partido ganador de unas elecciones por medio de un array de partidos y de sus respectivos resultados porcentuales.

La solución com RambdaJS sería parecida a la siguiente:

const parties = ['PSOE', 'Cs', 'Others', 'PP', 'UP'];
const percent = [22.66, 13.05, 10.76, 33.03, 21.1];

const getPartyWinner = R.compose(
    R.head,
    R.pluck(0),
    R.reverse,
    R.sortBy(R.prop(1)),
    R.zip
);

const partyWinner = getPartyWinner(parties, percent); // 'PP'

Como vemos, se nos queda un programa muy sencillo y con el uso de funciones muy genéricas. Expliquemos las funciones para entender el programa:

  • R.zip: esta función fusiona dos arrays en uno. Si no nosotros le pasamos [a, b], [c, d], el nos devolverá: [[a, c], [b, d]]. De esta manera fusionamos ambos arrays para poder trabajar con una única entrada.
  • R.sortBy: esta función se encarga de ordenar un array por el indice o clave que le indiquemos. De esta forma ordenamos de menor a mayor nuestro array por los porcentajes que han sacado los partidos.
  • R.reverse: es una función que devuelve un array ordenado al revés. De esta forma ordenamos los partidos de mayor a menor porcentaje.
  • R.pluck: nos permite quedarnos con los elementos del índice que le indiquemos. Es decir si, como en el ejemplo indicamos que queremos los elementos de índice 0 y pasamos este array: [[a, c], [b, d]], se quedará con: [a, b]. De esta forma nos quedamos solo con el nombre de los partidos.
  • R.head: se queda con la cabeza de un array. El primer elemento. En nuestro caso el partido ganador.

En RambdaJS también contamos con la función de orden superior ‘R.pipe’. Se diferencia con ‘R.compose’ en la forma en la que se indica la tubería de funciones a ejecutar. En ‘R.compose’ la primera en ejecutarse en la última en indicarse y en ‘R.pipe’, al revés. El ejemplo anterior con ‘R.pipe’ quedaría así:

const getPartyWinner = R.pipe(
    R.zip,
    R.sortBy(R.prop(1)),
    R.reverse,
    R.pluck(0),
    R.head
);

A mi particularmente, ‘R.pipe’ me parece más intuitivo.

Estas funciones son muy comunes en muchas librerías, lenguajes y frameworks funcionales. Si por alguna casualidad decides implementar tu mismo estas funcionalidades, intenta darle nombres parecidos ya que, de esa manera, muchos desarrolladores que hayan trabajado con funcional estarán muy familiarizado con esas firmas.

Si no es así, te recomiendo que inicies tu andadura con RambdaJS, tendrás todo lo necesario para realizar aplicaciones de forma declarativa.

Convivir con código puro e impuro

Muchos os estaréis diciendo durante toda la serie que esto de la programación funcional pinta muy bien, pero que todo lo que os muestro solo podría ser posible en un mundo ideal. Muchos os habréis preguntado qué ocurre con todo aquel código que es imposible que no sufra efectos laterales.

En una aplicación del mundo real será muy difícil que todo nuestro código se implemente con funciones puras. Habrá en muchas ocasiones donde los efectos laterales serán inevitables, sobre todo cuando necesitemos obtener datos introducidos por el usuario, o queramos obtener información del DOM, o cuando queramos escribir y leer en una base de datos o en un log, o queramos realizar llamadas asíncronas.

La idea principal de todo esto es conseguir aislar lo máximo posible todo esté código impuro en funciones simples que a ser posible solo tengan un cometido en particular. Si tenemos bien delimitado este tipo de código, tendremos claro donde puede que haya mayor porcentaje de ‘bugs’.

Estas funciones impuras podrán seguir usándose para componer aplicaciones más complejas con ‘R.compose’ o ‘R.pipe’, pero por lo menos las tendremos localizadas.

Conclusión

Hemos visto como el HTML se parece a nuestra idea de composición funcional. Hemos visto como la composición nos ayuda a crear cosas mayores y complejas. Si nos damos cuenta, poco a poco nuestro código es muy diferente a lo que estamos acostumbrados. Nuestro cerebro necesita asimilarlo poco a poco para que el cambio no sea tan brusco.

También hemos visto como RambdaJS nos ayuda a crear código funcional. RambdaJS ayuda mucho a JavaScript, creo que RambdaJS y TypeScript pueden ser dos grandes aliados en nuestra propuesta.

En el próximo post estudiaremos los combinadores, una serie de funciones que nos van a ayudar a controlar y gestionar el flujo de nuestra aplicación en un estilo funcional.

Nos leemos 🙂

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. Programación Funcional en JavaScript: Los métodos funcionales
  2. Programación Funcional en JavaScript: La recursividad

La modularidad funcional

  1. Programación Funcional en JavaScript: La aridad y las tuplas
  2. Programación Funcional en JavaScript: La currificación
  3. Programación Funcional en JavaScript: La composición

Imagen de portada | Trey Ratcliff

Anuncios

9 comments

  1. Pingback: Programación Funcional en JavaScript: Los combinadores | el.abismo = de[null]
  2. Pingback: Programación Funcional en JavaScript: Los funtores | el.abismo = de[null]
  3. Pingback: Programación Funcional en JavaScript: La mónada Maybe | el.abismo = de[null]
  4. Pingback: Programación Funcional en JavaScript: La mónada Either | el.abismo = de[null]
  5. Pingback: Programación Funcional en JavaScript: La mónada IO | el.abismo = de[null]
  6. Pingback: Programación Funcional en JavaScript: La evaluación perezosa | el.abismo = de[null]
  7. Pingback: Programación Funcional en JavaScript: La memoización | el.abismo = de[null]
  8. Pingback: Pequeños trucos para mejorar tu JavaScript | el.abismo = de[null]
  9. Xavi · abril 30

    Hola Jose,

    Primero decir que me esta encantando toda esta saga de programación funcional en javascript. Estoy deseando llegar a las mónadas pero antes quería ambientarme y me estoy leyendo todas las entradas anteriores.

    He visto la implementación de compose, y aunque este bien creo que puede despistar un poco por el uso de this, que en el contexto del tema no tendría ningún valor. Por otro lado, la implementación permite que el último parametro sea una función multiparámetro. Sin embargo, en el contexto en el que estamos hablando de funciones de un sólo parámetro creo que se podría simplificar la implementación para que sea más comprensible:

    function compose(…fns) {
    return function (a) {
    let result = a
    for (let i = fns.length – 1; i >= 0; –i) {
    result = fns[i](result)
    }
    }
    }

    De hecho, aunque esta siguiente opción la desaconsejo por no haberse explicado previamente el método .reduceRight es la que me parece más bonita:

    function compose(…fns) {
    return function (a) {
    return fns.reduceRight((result, fn) => fn(result), a)
    }
    }

    Me gusta

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