Composición de kleisli

August 21, 2019

...

Vamos a darle una vuelta a una función que vimos hace un par de posts atrás, la cual es esta:

const composeM = method => (...ms) => (
  ms.reduce((f, g) => x => g(x)[method](f))
)

Esta función si bien puede ser complicada, es muy poderosa ya que permite crear composiciones a libre demanda dependiendo del método que queramos encadenar.

Vamos a definir ahora un contenedor que vamos a utilizar para poder ir encadenando funciones:

const caja = [10]

const duplica = x => x * 2
const incrementa = x => x + 1

const nuevaCaja = caja
	.map(duplica)
	.map(incrementa)
	
console.log(nuevaCaja) // 21

Ahora veremos como podemos obtener el mismo resultado utilizando compose

const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x)
const caja = [10]

const duplica = x => x * 2
const incrementa = x => x + 1
const duplicaIncrementa = compose(incrementa, duplica)
const nuevaCaja = caja.map(duplicaIncrementa)
	
console.log(nuevaCaja) // 21

En el código anterior vimos como pudimos crear una función llamada duplicaIncrementa que nos permite obtener el mismo resultado sin tener que llamar múltiples veces a map. Esto es porque nuestra caja cumple con la siguiente ecuación:

caja.map(f).map(g) === caja.map(x => g(f(x)))

Importante, no estoy recomendando utilizar map constantemente para iterar arreglos, para los arreglos lo mejor es utilizar reduce y componer las funciones. Piensen esto solo para manejar un flujo de data.

Lo que nosotros vimos ahora fue como poder encadenar estas operaciones utilizando map, veamos si podemos encadenar estas operaciones modificando nuestra función de compose:

const composeMap = (...ms) =>
  ms.reduce((f, g) => x => g(x).map(f))

Este cambio que hicimos es sutil pero poderoso, vamos a analizarlo: ms son funciones que recibe nuestra función composeMap, estas se transforman a un arreglo para que podamos iterarlas, de ahí la sintaxis de los 3 puntos. Aplicamos reduce donde no le indicamos un valor inicial ya que este será una función como veremos más adelante. La función reducer hace lo siguiente:

const reducer = (f, g) => x => g(x).map(f)

f inicialmente no va a ser nada, el que nos importa en este momento es g, esta es la primera función que estamos iterando de izquierda a derecha, o sea que si llamamos a composeMap así:

const composeMap = (...ms) =>
  ms.reduce((f, g) => x => g(x).map(f))
  
const x = composeMap(
  funcion1,
  funcion2,
)

x('lala')

g es funcion1 la primera vez que se itera en nuestra función de reducer. Sin embargo nuestra función de reducer retorna una función, la cual ahora toma el valor de g, por ende, cuando ejecutemos x con el argumento de lala lo primero en ejecutarse será la función funcion1. Luego g será la función compuesta por sí misma! Vamos a ver como queda sin utilizar composeMap:

const composeMap = (...ms) =>
  ms.reduce((f, g) => x => g(x).map(f))

const comp = composeMap(
  funcion1,
  funcion2,
)

const comp2 = x => x.map(funcion1).map(funcion2)

comp === comp2 // en este caso, el resultado es el mismo

Esto es muy poderoso ya que podemos encadenar el método de map sin necesidad de mencionarlo, por lo que funciones que ya hayamos creado podemos componerlas sin ningún esfuerzo para que estas apliquen a arreglos. Y si la modificamos un poco podemos hacer que esta también aplique a promesas:

const composePromises = (...ms) =>
  ms.reduce((f, g) => x => g(x).then(f))

Ahora, podemos agregarle un argumento más para que podamos crear ambas funciones sin esfuerzo:

const composeM = method => (...ms) =>
  ms.reduce((f, g) => x => g(x)[method](f))

De esta manera podremos componer promesas, arreglos a cualquier otro tipo de dato donde debamos llamar siempre al mismo método para encadenarlo.

Espero que este post te haya gustado, puedes suscribirte en la cajita más abajo, recuerda seguirme en youtube y en twitter, los links más abajo en el párrafo donde sale mi foto.


Suscríbete

Suscríbete a la lista para más cursos, posts y videos tutoriales. Prometo no enviarte más de un correo semanal 🙏

Creado por Nicolás Schürmann ingeniero e instructor de software. Cuando no está programando, esta frente a una cámara dictando cursos, creyéndose youtuber o apoyando a sus alumnos. Puedes seguirlo en twitter o también suscribirte a su canal de youtube. Considera comprar sus cursos por este medio y así apoyas al instructor.