El currying es una técnica que transforma una función con múltiples parámetros en una secuencia de funciones, cada una con un solo parámetro.
En otras palabras, en lugar de pasar todos los argumentos a la función de una vez, el currying permite “descomponer” la función en pasos más simples.
El currying es un concepto proviene del ámbito matemático, y que ha encontrado un lugar importante en la programación funcional. En JavaScript, se ha vuelto un patrón muy popular.
Esto puede parecer complejo al principio. Pero, en en ocasiones, este enfoque permite mejorar la modularidad, la reutilización de código.
Currying en JavaScript
Para ilustrar el currying, supongamos un ejemplo sencillo. Imaginemos que tenemos una función simple que suma dos números:
function sumar(a, b) {
return a + b;
}
¿Nada especialmente llamativo, verdad? Veamos la versión curried de esta funcion.
function sumarCurried(a) {
return function(b) {
return a + b;
};
}
Ahora, en lugar de llamar a nuestra funcion sumar(a, b)
, tenemos una función que acepta un parámetro, y devuelve otra función que acepta otro parámetro.
Podemos utilizar nuestra versión sumarCurried
de la siguiente manera:
const sumar5 = sumarCurried(5);
console.log(sumar5(3)); // Output: 8
console.log(sumar5(10)); // Output: 15
En este ejemplo,
sumarCurried
toma un argumentoa
y devuelve una nueva función que espera el segundo argumentob
.- Esto nos permite crear funciones especializadas (como
sumar5
) que siempre suman un número fijo.
Es decir, ahora tenemos una función que genera funciones que suman un número determinado.
Currying con lambdas
Podemos usar funciones lambda para crear una versión más compacta de currying. El comportamiento sigue siendo el mismo, pero el uso de lambdas lo hace más conciso y más acorde con el estilo moderno de JavaScript.
const sumar = a => b => a + b + c;
const resultado = sumar(2)(3); // Resultado: 5
En este ejemplo,
- La función
sumar
ahora está definida como una serie de funciones anidadas utilizando flechas. - Cada función retorna otra función que acepta el siguiente argumento, hasta que se alcanza el cálculo final con
a + b
.
Implementación de Currying
Hemos visto como convertir una función en una versión “curried” de forma manual, en el ejemplo anterior con la función suma
.
Sin embargo, lo normal es que hagamos alguna solución genérica, para convertir cualquier función. Vamos a ver alguna de ellas.
Función utilitaria
El currying se puede implementar con una función utilitaria sencilla. Por ejemplo
function currying(fn) {
return function(arg1) {
return function(arg2) {
return fn(arg1, arg2);
};
};
}
// Uso
function multiplicar(a, b) {
return a * b;
}
const multiplicarCurried = currying(multiplicar);
const multiplicarPor2 = multiplicarCurried(2);
console.log(multiplicarPor2(4)); // Output: 8
En este ejemplo, currying
convierte una función de dos argumentos en una secuencia de funciones unarias.
Funcion utilitaria genérica
También podemos crear una función utilitaria para currying más avanzado, que un número arbitrario de argumentos.
function currying(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
} else {
return function(...args2) {
return curried(...args, ...args2);
};
}
};
}
// Uso
function multiplicar(a, b, c) {
return a * b * c;
}
const multiplicarCurried = currying(multiplicar);
const multiplicarPor2y3 = multiplicarCurried(2)(3);
console.log(multiplicarPor2y3(4)); // Output: 24
En este ejemplo, la función currying
toma cualquier función fn
y devuelve una versión curried de ella. Esto permite que los argumentos se pasen uno a la vez o en grupos.
Uso de librerías
Muchas librerías y frameworks de JavaScript proporcionan soporte para currying. Por ejemplo, Lodash tiene una función _.curry
que facilita la creación de funciones curried.
const _ = require('lodash');
const curriedSum = _.curry(function(a, b, c) {
return a + b + c;
});
console.log(curriedSum(1)(2)(3)); // 6
Composición de funciones
El currying y la composición de funciones están estrechamente relacionados. Mientras que el currying descompone funciones en una serie de funciones unarias, la composición de funciones permite combinarlas para construir nuevas funciones más complejas.
const sumar = a => b => a + b;
const multiplicar = a => b => a * b;
// Combinamos las funciones para crear una nueva
const sumaMultiplicada = (a, b, c) => multiplicar(a)(sumar(b)(c));
console.log(sumaMultiplicada(2, 3, 4)); // Output: 14
En este ejemplo,
sumar
ymultiplicar
son funciones curried que devuelven una nueva función al recibir un argumento.sumaMultiplicada
, utiliza composición. Primero,sumar(b)(c)
calcula la suma deb
yc
, devolviendo7
. Luego,multiplicar(a)(7)
multiplica el resultado pora
, dando14
.
Es decir, hemos compuesto dos funciones curried (sumar
y multiplicar
) para formar una nueva función más compleja (sumaMultiplicada
)
En cierto modo, la composición de funciones es el paso inverso al currying: en lugar de dividir, estamos combinando.
Ejemplos prácticos
Cálculo de descuentos
Veamos un ejemplo práctico de cómo podríamos utilizar el currying en una aplicación real:
function descuento(porcentaje) {
return function(precio) {
return precio * (1 - porcentaje);
};
}
const aplicarDescuento10 = descuento(0.1);
const aplicarDescuento20 = descuento(0.2);
console.log(aplicarDescuento10(100)); // Resultado: 90
console.log(aplicarDescuento20(100)); // Resultado: 80
En este ejemplo, descuento
es una función curried que toma un porcentaje de descuento y devuelve otra función que toma el precio y calcula el precio con el descuento aplicado
Configuración de funciones
El currying es útil para configurar funciones con argumentos predeterminados.
function configurarSaludo(saludo) {
return function(nombre) {
return `${saludo}, ${nombre}!`;
};
}
const saludoFormal = configurarSaludo('Buenos días');
console.log(saludoFormal('Juan')); // Buenos días, Juan!
Composición de funciones
El currying facilita la composición de funciones, donde se combinan varias funciones para formar una nueva función.
function sumar(a, b) {
return a + b;
}
function multiplicar(a, b) {
return a * b;
}
const curriedSum = currying(sumar);
const curriedMultiply = currying(multiplicar);
const resultado = curriedMultiply(curriedSum(2)(3))(4);
console.log(resultado); // 20
En este ejemplo, curriedSum(2)(3)
devuelve 5, y curriedMultiply(5)(4)
devuelve 20.