Bien vamos con la última de las características que tiene la programación funcional antes de adentrarnos puramente en propiedades funcionales. En este caso vamos a ver en detalle que es eso de Composición de funciones.
Esto realmente no tiene mucha ciencia, el propio nombre nos indica de que va, se trata de aplicar funciones en cadena para lógicas más complejas. Esto se suele ver en ciertas librerias como composition, pipe, flow..., primero veremos como aplicar esto de forma nativa y ya veremos algún atajo con alguna libreria.
Continuemos con la teoria de uno de los ejemplos anteriores que es la de crear un player inicial para un juego:
const setName = (name) => (player) => {
player.name = name
return player
}
const setPowers = (player) => {
const availablePowers = ['fly', 'invisibility', 'bulletproof', 'superstrength']
player.powers = availablePowers[Math.floor(Math.random() * availablePowers.length)]
return player
}
const setWeapon = (player) => {
const availableWeapons = ['gun', 'katana', 'ninja star', 'grenade']
player.weapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]
return player
}
const newPlayer = setWeapon(setPowers(setName('Ninja')({})))
console.log(newPlayer)
Si nos fijamos en el contenido de newPlayer, es donde tenemos nuestra composición de funciones, lo que se suele ver como:
f(g(x))
En este caso puede que sea facil de entender lo que estamos haciendo en este caso, pero si tuvieramos esta forma en soluciones más complejas puede que no nos enteremos de nada, por lo que veamos como ir mejorando nuestra teoria de la composición.
Lo primero que haremos será crear nuestro propio método compose:
const compose = (...fns) => x => {
return fns.reduce((previous, current) => {
return current(previous)
}, x)
}
Este método compose lo que hace es recibir una cantidad n de funciones que debe aplicar sobre un valor inicial x. En este caso lo hace de izquierda a derecha, muchas veces se usa reduceRight para hacerlo de derecha a izquierda, depende de cuales sean los métodos a usar para la composición nos interesa más una dirección u otra (por defecto derecha a izquierda aunque a mí me gusta más izquierda a derecha).
Ahora con esta función compose solo tenemos que usarla:
const compose = (...fns) => x => {
return fns.reduce((previous, current) => {
return current(previous)
}, x)
}
const setName = (name) => (player) => {
player.name = name
return player
}
const setPowers = (player) => {
const availablePowers = ['fly', 'invisibility', 'bulletproof', 'superstrength']
player.powers = availablePowers[Math.floor(Math.random() * availablePowers.length)]
return player
}
const setWeapon = (player) => {
const availableWeapons = ['gun', 'katana', 'ninja star', 'grenade']
player.weapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]
return player
}
const newPlayer = setWeapon(setPowers(setName('Ninja')({})))
const otherPlayer = compose(setName('Ninja'), setPowers, setWeapon)({})
console.log(newPlayer)
console.log(otherPlayer)
Como podemos ver ambas funcionan correctamente y es un poco más sencillo de entender lo que estamos haciendo. Recuerdo que este ejemplo es un poco forzado es solo orientativo para ver la funcionalidad compose.
Veamos el ejemplo con Ramda
const R = require('ramda')
const setName = (name) => (player) => {
player.name = name
return player
}
const setPowers = (player) => {
const availablePowers = ['fly', 'invisibility', 'bulletproof', 'superstrength']
player.powers = availablePowers[Math.floor(Math.random() * availablePowers.length)]
return player
}
const setWeapon = (player) => {
const availableWeapons = ['gun', 'katana', 'ninja star', 'grenade']
player.weapon = availableWeapons[Math.floor(Math.random() * availableWeapons.length)]
return player
}
const newPlayer = setWeapon(setPowers(setName('Ninja')({})))
// Aquí en lugar de compose usamos el método en cuestión de R que es pipe
const otherPlayer = R.pipe(setName('Ninja'), setPowers, setWeapon)({})
console.log(newPlayer)
console.log(otherPlayer)
Solo tenemos que usar pipe para hacerlo de izquierda a derecha o compose para hacerlo de derecha a izquierda
Un detalle importante a tener en cuenta y que hacemos uso en el ejemplo, es lo que se conoce como programación tacita o point free, este concepto se refiere a la omisión de los parámetros de los métodos cuando lo usamos por ejemplo en una composición como esta que hemos visto, debido a que los valores de retorno de unos métodos son los de entrada de otros por lo que como el proceso se realiza automáticamente lo podemos omitir. En este caso que hemos visto siempre se retorna el mismo objeto, pero pudiera ser cualquier otro caso mientras que se cumpla este concepto:
funcA :: A -> B
funcB :: B -> C
// Esto lo podemos encadenar porque funcB espera como valor de entrada B que es lo que devuelve funcA
funcC:: funcA + funcB :: A -> B -> C
Creo que con esto se entiende más o menos lo básico sobre la composición, en los próximos capítulos veremos elementos más puramente funcionales como pueden ser los Funtores, una abrazooooo

