Javascript. Currying
Descubre en qué consiste la técnica “currying” y cómo podemos aplicarla más fácilmente gracias a ES6
Hace tiempo escribí un artículo hablando acerca de la programación funcional con el fin de que todos aquellos que no conocierais ese término pudierais familiarizaros con los conceptos básicos:
Hoy quiero abordar en este artículo el concepto de “currying”, que gracias a las novedades que trajo en su momento ES6 resulta muy sencillo de aplicar y nos va a permitir crear aplicaciones con un estilo funcional y mucho más reutilizables.
Presta atención porque seguro que al terminar el artículo descubres mil casos donde aplicar lo que estoy a punto de contarte.
¿En qué consiste el currying?
“Currying” es la técnica que nos permite invocar una función con menos parámetros de los que esperaría inicialmente, dejando para un momento posterior la especificación de esos parámetros ausentes.
Vamos a verlo con un ejemplo muy sencillo. Imagina que tenemos definida la función multuply
de esta forma:
const multiply = (a, b) => a * b;
Cuando la queramos invocar, estaremos obligados a especificar sus dos argumentos:
multiply(2, 3)
Sin embargo esa misma función podemos reescribirla mediante la técnica “currying” del siguiente modo:
const curriedMultiply = a => b => a * b;
Si te resulta difícil ver lo que sucede con notación flecha puedes verlo también así:
const curriedMultiply = function(a) {
return function(b) {
return a * b;
}
}
Con esta versión “currificada”, podremos invocar nuestra función del siguiente modo:
curriedMultiply(2)(3)
O, como decía al principio, delegar para el futuro la ejecución completa de la función:
const multiplyBy2 = curriedMultiply(2);// latermultiplyBy2(3);
Miniaplicaciones
Sé que este es un ejemplo muy sencillo pero una de las ideas que subyacen detrás de la técnica “currying” es la de crear mini-aplicaciones.
Por ejemplo, imagina una función que se encargue de loggear mensajes en la consola:
function log(level, date, message) {
console.log(`[${level}]: ${date} - ${message}`);
}log('critical', new Date(), 'Some message');
Cada vez que tenemos que invocar esa función debemos especificar los 3 argumentos. Sin embargo, si la “currificamos” podemos tener una mini aplicación que directamente loguee mensajes “críticos”:
function curriedLog = level => date => message => {
console.log(`[${level}]: ${date} - ${message}`);
}
De este modo tendríamos:
const logCritical = curriedLog('critical');
Y posteriormente podríamos usarla para loggear mensajes críticos sin repetir una y otra vez el string ‘critical’:
logCritical(new Date())('Some message');
Composición de funciones
Otra de las ventajas de trabajar con funciones “currificadas” es la posibilidad de componerlas.
La composición de funciones consiste básicamente en aplicar una función tras otra a un valor:
const sum2 = x => x + 2;
const triplicate = x => x * 3;
const sum5 = x => x + 5;sum5(sum2(triplicate(4))); // 19
Sin embargo este estilo es muy “imperativo”. Es decir, estamos obligados a ir escribiendo una tras otra cada función.
Sin embargo gracias a reduce
podemos obtener una función genérica que nos permita componer funciones:
const compose = (...fns) => args => fns.reduceRight((arg, fn) => fn(arg), args);
De este modo, la composición del ejemplo anterior queda mucho más sencilla:
compose(sum5, sum2, triplicate)(4);
¿Y qué relación tiene esto con el currying?
Supón que tenemos las siguientes funciones:
function log(label, value) {
console.log(`[${label}]: ${value}`);
return value;
}const sum2 = x => x + 2;const triplicate = x => x * 3;
Si quisiéramos componerlas como en el ejemplo anterior no podríamos:
compose(sum2, log, triplicate)(4);
Porque tras la llamada triplicate(4)
(que devuelve 12), lo que se invocaría sería log(12)
. Puesto que la función espera 2 argumentos la composición termina devolviendo NaN
.
Sin embargo, si currificamos log
:
const curriedLog = label => value => {
console.log(`[${label}]: ${value}`);
return value;
}const sum2 = x => x + 2;const triplicate = x => x * 3;
Ahora podríamos invocar:
compose(sum2, curriedLog('Invoked before sum'), triplicate)(4);
Ya que lo que estaríamos invocando ahora sería la siguiente “parte” del log, es decir, la correspondiente a:
value => {
console.log(`[${label}]: ${value}`);
return value;
}
la cual ya sólo necesita un argumento.
Conclusiones
Como ves, esta técnica resulta muy útil en el momento en que necesitamos comenzar a “trocear” nuestras funciones de cara a que podamos posteriormente componerlas.
Por supuesto también resulta muy útil al permitirnos crear “miniAplicaciones” a partir de otras funciones como en el ejemplo que vimos, donde definiendo la función multiply
de esta forma:
const curriedMultiply = a => b => a * b;
Podemos crear multiplicadores reaprovechando una y otra vez la lógica:
const duplicate = curriedMultiply(2)
Si quieres leer más sobre esta técnica te dejo un par de artículos que seguro que te resultarán interesantes:
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇
Apóyame en Patreon
🧡🧡🧡 Gracias a: Joseba, Óscar, Alex y Jorge.