Currificación en JavaScript
La currificación es una técnica avanzada de manipulación de funciones que se usa en diversos lenguajes de programación, incluyendo JavaScript.
La currificación convierte una función que puede ser invocada como f(a, b, c)
en una que se puede invocar como f(a)(b)(c)
.
La currificación no ejecuta la función, solo la transforma.
Vamos a ver un ejemplo para entender mejor el concepto y luego explorar sus aplicaciones prácticas.
Crearemos una función auxiliar curry(f)
que currifica una función f
de dos argumentos. Es decir, curry(f)
transformará una función de la forma f(a, b)
en una que se puede invocar como f(a)(b)
:
function curry(f) { // curry(f) realiza la transformación de currificación
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// uso
function sum(a, b) {
return a + b;
}
let curriedSum = curry(sum);
alert(curriedSum(1)(2)); // 3
Como se puede ver, la implementación es simple: dos funciones anidadas.
El resultado de curry(func)
es una función que toma un argumento a
. Cuando se invoca como curriedSum(1)
, el argumento se guarda y devuelve una nueva función que toma otro argumento b
. Luego, esta función llama a la función original sum
con los dos argumentos. Las implementaciones más sofisticadas de currificación, como _.curry
de la biblioteca lodash, permiten invocar una función de manera normal o parcialmente:
function sum(a, b) {
return a + b;
}
let curriedSum = _.curry(sum); // usando _.curry de lodash
alert(curriedSum(1, 2)); // 3, invocación normal
alert(curriedSum(1)(2)); // 3, invocación parcial
¿Por qué usar currificación?
Para entender las ventajas, veamos un ejemplo práctico.
Supongamos que tenemos una función de registro log(date, importance, message)
que formatea y muestra información. En proyectos reales, estas funciones pueden tener características útiles, como enviar registros a través de la red. Aquí, solo usaremos alert
:
function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
Vamos a currificarla:
log = _.curry(log);
Después de esto, log
funciona normalmente:a
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
…pero también se puede invocar de manera currificada:
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
Ahora podemos crear fácilmente una función para registrar eventos actuales:
// logNow será una versión parcial de log con el primer argumento fijo
let logNow = log(new Date());
// uso
logNow("INFO", "message"); // [HH:mm] [INFO] message
Ahora logNow
es log
con el primer argumento fijo, es decir, una «función parcialmente aplicada» o «parcial» para abreviar.
Podemos ir más allá y crear una función conveniente para los registros de depuración actuales:
let debugNow = logNow("DEBUG");
debugNow("message"); // [HH:mm] [DEBUG] message
Así:
- No perdemos la funcionalidad original:
log
todavía puede ser llamada normalmente. - Podemos crear fácilmente funciones parciales, como registros para el día de hoy.
Implementación avanzada de currificación
Para quienes quieran profundizar, aquí está la implementación de una currificación «avanzada» para funciones con múltiples argumentos.
Es bastante breve:
- No perdemos la funcionalidad original:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
Ejemplos de uso:
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert(curriedSum(1, 2, 3)); // 6, llamada normal
alert(curriedSum(1)(2, 3)); // 6, currificación parcial
alert(curriedSum(1)(2)(3)); // 6, currificación completa
El nuevo curry
puede parecer complicado, pero es fácil de entender.
El resultado de curry(func)
es la función curried
que se ve así:
function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
}
Cuando se ejecuta, hay dos posibles rutas:
- Si el número de argumentos
args
es igual al que la función originalfunc
espera (func.length), se invoca la función original confunc.apply
. - De lo contrario, se obtiene una función parcial: no se llama aún a
func
. En cambio, se devuelve otra función que aplicarácurried
con los argumentos anteriores junto con los nuevos.
Luego, en una nueva llamada, se obtiene otro parcial (si no hay suficientes argumentos) o finalmente el resultado.
Funciones con número fijo de argumentos
La currificación requiere que la función tenga un número fijo de argumentos.
Una función que utiliza parámetros variables, como f(...args)
, no puede ser currificada.
Más allá de la currificación
Por definición, la currificación debería convertir sum(a, b, c)
en sum(a)(b)(c)
.
Pero la mayoría de las implementaciones de currificación en JavaScript son avanzadas, como se describió anteriormente: también permiten invocar la función de manera normal y devuelven un parcial si no se proporciona el número suficiente de argumentos.
Resumen
- La currificación es una transformación que convierte
f(a, b, c)
enf(a)(b)(c)
. - Las implementaciones de currificación en JavaScript generalmente permiten invocar la función de manera normal y devuelven un parcial si no se proporcionan suficientes argumentos.
- La currificación facilita la creación de funciones parciales. Como vimos en el ejemplo de registro, después de currificar la función universal de tres argumentos
log(date, importance, message)
, podemos obtener parciales fácilmente cuando se llama con un argumento (comolog(date)
) o dos argumentos (comolog(date, importance)
).