Desmitificando el currying en JavaScript: cierres, aplicación parcial y reutilización
Aprenda cómo el currying convierte funciones de JavaScript con múltiples argumentos en cadenas reutilizables con un solo argumento, cómo difiere de la aplicación parcial y cuándo omitirlo.
Si sigues pasando siempre el mismo primer argumento a una función una y otra vez, se cobra un pequeño costo por repetición en cada llamada. El currying te permite fijar parte de los argumentos de una función una vez y reutilizar el resultado. A continuación verás qué es el currying, cómo las clausuras lo hacen posible, en qué se diferencia de la aplicación parcial y cuándo realmente mejora el código.
Qué significa el currying
El currying convierte una función que recibe varios argumentos en una secuencia de funciones, donde cada una recibe exactamente uno. Una llamada normal proporciona todo de una sola vez:
f(a, b, c)
La forma curried proporciona un valor por llamada, y cada llamada devuelve la siguiente función de la cadena:
f(a)(b)(c)
Cada función intermedia recuerda los valores de las llamadas anteriores. Esa memoria proviene de las clausuras, que se explican más abajo.
Convierte una función normal
Comienza con una operación de suma ordinaria que requiere dos argumentos:
function sum(a, b) {
return a + b;
}
sum(2, 3); // 5
Un pequeño ayudante puede envolver cualquier función de dos argumentos: la función exterior toma el primer valor, devuelve una función que toma el segundo y solo entonces llama a la original con ambos:
function curry(fn) {
return function (a) {
return function (b) {
return fn(a, b);
};
};
}
const curriedSum = curry(sum);
curriedSum(2)(3); // 5
Aspectos a tener en cuenta:
curryes de orden superior: tanto su entrada como su salida son funciones.- Cada función que devuelve espera un solo valor.
- La función interior aún puede acceder a
adespués de que la llamada exterior haya finalizado, ya que mantiene su referencia.
Este ayudante solo maneja exactamente dos parámetros. Las versiones multiusos verifican fn.length y siguen recopilando argumentos hasta que haya suficientes, pero la idea es idéntica.
Un caso práctico: registradores especializados
La aritmética oculta los beneficios, así que considere un registrador que acepte un nivel de severidad y un mensaje:
function log(level, message) {
console.log(`[${level}] ${message}`);
}
Los lugares donde se realizan las llamadas comienzan rápidamente a repetir el mismo nivel:
log("ERROR", "Something broke");
log("ERROR", "Invalid token");
log("ERROR", "API failed");
Un logger curried permite crear loggers preconfigurados una sola vez:
function log(level) {
return function (message) {
console.log(`[${level}] ${message}`);
};
}
const errorLog = log("ERROR");
const infoLog = log("INFO");
errorLog("Something broke");
errorLog("Invalid token");
infoLog("User logged in");
El nivel se fija cuando se crean errorLog y infoLog. Los lugares de llamada se acortan, la intención se entiende fácilmente y las funciones especializadas pueden exportarse o injertarse en otro lugar. Esa combinación de reutilización y expresividad es la razón principal para usar el currying.
Aplicación parcial
La aplicación parcial significa rellenar previamente algunos argumentos de una función existente para obtener una nueva función, sin cambiar la estructura original. JavaScript lo soporta de forma nativa a través de bind:
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
double(5); // 10
El primer argumento de bind establece this, el cual multiply ignora, por lo que resulta en null. multiply sigue requiriendo dos argumentos; double es simplemente una variante en la que se proporciona el primero de ellos.
Currying versus aplicación parcial
Ambos comparten un objetivo pero no un mecanismo:
- El currying reestructura una función en una cadena de funciones con un solo argumento.
- La aplicación parcial completa algunos argumentos de inmediato y mantiene la estructura original de la función.
Cada paso de una cadena curriada es, en efecto, una aplicación parcial con un solo argumento, pero la aplicación parcial nunca requiere currying.
Currying abierto
Una variante lúdica sigue aceptando valores hasta que se finaliza la cadena con una llamada vacía:
let infiniteSum = function (a) {
return function (b) {
if (b !== undefined) return infiniteSum(a + b);
return a;
};
};
infiniteSum(1)(2)(3)(4)(); // 10
Así es como se despliega:
- Cada llamada con un valor lo agrega al total acumulado y devuelve una nueva función que lleva ese total.
- La última llamada a
()dejabcomoundefined, por lo que se devuelve la suma. - Las cierres de función transportan el total de un paso al siguiente.
La verificación explícita b !== undefined es importante. Una prueba de verdad como if (b) trataría a 0 como el terminador, de modo que infiniteSum(1)(0)(2)() se detendría en cero en lugar de devolver 3. Este patrón es un excelente ejercicio sobre cierres de función y una pregunta común en entrevistas, pero la llamada final fácil de olvidar lo convierte en una mala opción por defecto para APIs de producción.
Las cierres de función hacen que todo funcione
Aquí está el sumador curried más simple:
function add(a) {
return function (b) {
return a + b;
};
}
add(3)(4); // 7
add(3) devuelve la función interna y termina, pero esa función interna aún puede leer a. Eso es un cierre: una función mantiene el acceso a las variables del ámbito en el que fue definida, incluso después de que la función externa haya devuelto un valor. Dado que el valor capturado persiste, se puede almacenar y reutilizar la función parcialmente aplicada:
const add3 = add(3);
add3(4); // 7
add3(10); // 13
Para obtener más información sobre cómo se resuelven esas variables capturadas, consulte cómo resuelve la cadena de ámbitos las variables en JavaScript.
Cuándo usarlo y cuándo omitirlo
El currying es útil cuando:
- los mismos argumentos iniciales aparecen en muchos puntos de llamada
- hace que el código sea más claro en lugar de más enigmático
- se desean funciones pequeñas, componibles y reutilizables
Omite este enfoque cuando:
- una función simple y única no se beneficia al dividirla
- tu equipo no está familiarizado con este patrón
- las llamadas en cadena dificultan la legibilidad
Conclusión
El currying se trata realmente de ordenar las entradas de una función según el momento en que se conocen: la configuración, como el nivel de registro o la URL base, se fija temprano, mientras que los datos de cada llamada llegan más tarde. Las clausuras se encargan de recordar esto, bind permite una aplicación parcial más ligera cuando no se necesita una cadena completa, y la pregunta clave siempre es si el código de llamada resulta más fácil de leer y reutilizar.
Lecturas relacionadas
- Comprender los cierres en JavaScript y el bucle de eventos — Aprenda cómo los cierres conservan las variables externas y cómo el bucle de eventos organiza la pila de llamadas, las microtareas y las macrotareas mediante ejemplos de código prácticos.
- Cómo JavaScript resuelve realmente las variables mediante su cadena de alcance — Explica el mecanismo de la cadena de alcance léxico detrás de la búsqueda de variables, por qué var causa errores en bucles y cómo los cierres surgen naturalmente de ello.