Composición de funciones

Como vimos con anterioridad, pudimos ver un uso interesante de las funciones puras, el funcionamiento y flexibilidad de las FOS. Pero tal vez habrán notado que se iteró innecesariamente en algunos casos como en los últimos dos ejemplos de reduce.

Vimos como se recorre tres veces un array, y aplicar individualmente una función pura. Pero luego se podía aplicar las tres funciones juntas en una sola iteración del array y obtener el mismo resultado. Esto es un concepto matemático llamado composición de funciones y es el proceso inverso a la separación de las funciones en la definición de lambda.

Definición

La composición de dos funciones, es simplemente pasar los resultados de una primera función como argumentos de una segunda función. Se denota como

// matematicamente
H = F o G

// JS
const H = F(G)

// otra definición usando arrow functions
const definicion = ( ((x, y, z) => {...}), otros, paramentros) => {...}

La ventaja de esto, es que podemos crear una función de una complejidad mayor, con pequeñas funciones simples. Se utiliza mayormente, para evitar ciclos innecesarios de iteración en el uso de FOS.

Ejemplo

const listaDePedidos = [
{ plato: 'pizza',       precio:  20, cantidad: 2},
{ plato: 'pasta',       precio:  15, cantidad: 1},
{ plato: 'ensalada',    precio:  5,  cantidad: 4},
{ plato: 'hamburguesa', precio:  12, cantidad: 2},
{ plato: 'bebida',      precio:  5,  cantidad: 9}
]
let multiplicadoPorCantidad = listaDePedidos.map(x => x.precio * x.cantidad)
let agregarImpuesto = multiplicadoPorCantidad.map(x => 1.21 * x)
let agregarPropina = agregarImpuesto.map(x => 1.1 * x)
let totalAPagar = agregarPropina.reduce((previo, x) => previo + x, 0)

Como podemos ver. Nuestro código esta intencionalmente mal hecho, podemos verlo por la simpleza del ejemplo. No siempre es fácil detectar el uso innecesario de recursos en casos más complejos. Pero podríamos mejorar nuestro código componiendo funciones

const multiplicadoPorCantidad = x => x.precio * x.cantidad
const agregarImpuesto = x => 1.21 * x
const agregarPropina = x => 1.1 * x
const aplicarPago = x => agregarPropina(agregarImpuesto(multiplicadoPorCantidad(x)))

let total = listaDePedidos.reduce((acc, cur) => acc + aplicarPago(cur), 0) // 191.664

Comparativa

Dije en un principio que íbamos a comparar JS contra otros lenguajes. Y quiero resaltar que existen otros lenguajes, con un enfoque más puro a la programación funcional, sin ser funcionales, como Python.

Python es un lenguaje de muy alto nivel al igual que JS, que busca brindarnos herramientas para que no nos tengamos que preocupar por hacer bien las cosas, sino por hacerlas.

datos = [1,1,1,1,1]

def fun1(x):
	print("Primera iteración")
	return x+1

def fun2(x):
	print("Segunda iteración")
	return x+1

def fun3(x):
	print("Tercera iteración")
	return x+1

datos1 = map(fun1, datos)
datos2 = map(fun2, datos1)
datos3 = map(fun3, datos2)

for i in datos3:
	print(i)

En este ejemplo trivial de código, habiendo aprendido el funcionamiento de map, la intuición imperativa nos dice que obtendríamos por cada definición, cinco veces el texto de «Primera iteración», luego cinco veces «Segunda iteración» y así sucesivamente hasta tener el número cuatro, cinco veces. Pero como python espera que utilicemos funciones puras, hace una definición perezosa de como debería de ser los datos, y hasta no obtener un punto en el que es obligatoria la evaluación, no se evalúa.
Entonces, la salida de este script, es la siguiente

Primera iteración
Segunda iteración
Tercera iteración
4

repetida cinco veces. El total de iteraciones que hace el script es cinco, ya que evalúa que no necesita iterar. Ni siquiera existen listas en memoria llamados datos1 o datos2. Sino, que al concatenar las funciones puras, va a tener el mismo resultado deseado.

Conclusión

La composición de funciones nos permite simplificar las funciones grandes y complejas separando en pequeñas funciones atómicas mucho más simples de probar. Existen varias herramientas que nos ayudan con esta tarea. Ramda es una de estas herramientas, con su función compose, le podemos pasar un número indeterminado de funciones, y las concatena, devolviendo una función que recibe un parámetro, y devuelve un valor.

En un futuro no tan lejano, la implementación del operador pipe va a encontrarse en los motores de los navegadores para simplificar esta tarea, lo que convertiría

let precioConImpuestoYPropina = R.compose( agregarImpuesto, agregarPropina )
let precioConImpuestoYPropina = agregarImpuesto |> agregarPropina

Otro uso interesante de composición, es en conjunto de la currifición. Podemos aplicar parcialmente nuestro código inyectando funciones que compongan un comportamiento más complejo, y no solamente valores.

Seguimos avanzando con nuestro ejemplo. En esta ocasión le agregamos los eventos para poder cambiar el orden de las columnas de la tabla.

Comparte este artículo

Entra en la discusión y deja tu comentario

Veces