Skip to content

Capítulo 04: Currying

No puedes vivir si vivir es sin él (Can't Live If Livin' Is Without You)

Mi padre una vez explicó que hay cosas que creemos prescindibles hasta que las adquirimos. El microondas es un ejemplo. Los smartphones, otro. Los que tenemos cierta edad recordaremos una vida plena sin internet. Para mí, el currying está en esta lista.

El concepto es simple: puedes llamar a una función con menos argumentos de los que espera. Esto devuelve una nueva función que recibe los argumentos restantes.

Puedes elegir llamarla de una vez o simplemente proporcionar cada argumento gradualmente.

js
const add = x => y => x + y;
const increment = add(1);
const addTen = add(10);

increment(2); // 3
addTen(2); // 12

Aquí creamos una función add que toma un argumento y devuelve otra función. Al llamarla, la función devuelta 'recuerda' el primer argumento mediante el cierre (closure). Sin embargo, llamarla con ambos argumentos a la vez es engorroso, por lo que podemos usar una función auxiliar llamada curry para simplificar su definición y uso.

Definamos algunas funciones currificadas para ejemplificar. De aquí en adelante, usaremos nuestra función curry definida en el Apéndice A - Funciones Esenciales.

js
const match = curry((what, s) => s.match(what));
const replace = curry((what, replacement, s) => s.replace(what, replacement));
const filter = curry((f, xs) => xs.filter(f));
const map = curry((f, xs) => xs.map(f));

El patrón seguido es simple pero crucial: posicioné estratégicamente los datos a procesar (String, Array) como último argumento. La razón quedará clara al usarlas.

(La sintaxis /r/g es una expresión regular para coincidir con todas las letras 'r'. Puedes leer más sobre expresiones regulares si lo deseas.)

js
match(/r/g, 'hello world'); // [ 'r' ]

const hasLetterR = match(/r/g); // x => x.match(/r/g)
hasLetterR('hello world'); // [ 'r' ]
hasLetterR('just j and s and t etc'); // null

filter(hasLetterR, ['rock and roll', 'smooth jazz']); // ['rock and roll']

const removeStringsWithoutRs = filter(hasLetterR); // xs => xs.filter(x => x.match(/r/g))
removeStringsWithoutRs(['rock and roll', 'smooth jazz', 'drum circle']); // ['rock and roll', 'drum circle']

const noVowels = replace(/[aeiou]/ig); // (r,x) => x.replace(/[aeiou]/ig, r)
const censored = noVowels('*'); // x => x.replace(/[aeiou]/ig, '*')
censored('Chocolate Rain'); // 'Ch*c*l*t* R**n'

Esto demuestra la capacidad de "precargar" una función con uno o dos argumentos para obtener una nueva función que los recuerde.

Te recomiendo clonar el repositorio Mostly Adequate (git clone https://github.com/MostlyAdequate/mostly-adequate-guide.git), copiar el código anterior y probarlo en el REPL. La función curry y demás elementos de los apéndices están disponibles en el módulo support/index.js.

Alternativamente, consulta la versión publicada en npm:

npm install @mostly-adequate/support

Más que un juego de palabras / Ingrediente clave

El currying tiene múltiples usos. Podemos crear funciones específicas (como hasLetterR, removeStringsWithoutRs y censored) simplemente proporcionando argumentos base.

También podemos convertir cualquier función que opere en elementos individuales en una que opere en arrays simplemente envolviéndola con map:

js
const getChildren = x => x.childNodes;
const allTheChildren = map(getChildren);

Proporcionar menos argumentos de los esperados se llama aplicación parcial. Esto reduce código boilerplate. Compara la función allTheChildren anterior con la versión no currificada de map en lodash (con orden de argumentos distinto):

js
const allTheChildren = elements => map(elements, getChildren);

Normalmente no definimos funciones para arrays, pues podemos usar map(getChildren) directamente. Lo mismo aplica para sort, filter y otras funciones de orden superior (que toman o devuelven funciones).

Las funciones puras mapean 1 entrada → 1 salida. El currying hace exactamente esto: cada argumento devuelve una nueva función que espera los restantes. Eso, amigo lector, es 1 entrada → 1 salida.

Incluso si la salida es otra función, sigue siendo pura. Permitimos múltiples argumentos por conveniencia, pero se considera solo una eliminación de paréntesis redundantes.

En resumen

El currying es práctico y lo uso diariamente. Es una herramienta clave que hace la programación funcional más concisa.

Podemos crear funciones útiles al vuelo proporcionando algunos argumentos, manteniendo además la definición matemática de función pura.

Ahora aprendamos otra herramienta esencial llamada compose.

Capítulo 05: Programar mediante composición

Ejercicios

Nota sobre los ejercicios

En este libro encontrarás secciones de ejercicios como esta. Puedes resolverlos directamente en el navegador si lees desde gitbook (recomendado).

Para todos los ejercicios, tienes funciones auxiliares disponibles globalmente. Todo lo definido en los Apéndices A, B y C está accesible. Además, algunos ejercicios incluyen funciones específicas para su contexto.

Tip: Envía tu solución con Ctrl + Enter en el editor integrado.

Ejecutar ejercicios localmente (opcional)

Si prefieres usar tu propio editor:

  • clona el repositorio (git clone git@github.com:MostlyAdequate/mostly-adequate-guide.git)
  • accede a la sección de ejercicios (cd mostly-adequate-guide/exercises)
  • usa la versión de node v10.22.1 (ej. nvm install). Más detalles en el README
  • instala dependencias con npm (npm install)
  • completa las respuestas modificando archivos exercise_* en cada capítulo
  • ejecuta la corrección con npm (ej. npm run ch04)

Los tests unitarios verificaran tus respuestas y darán pistas. Las soluciones están en archivos *solution_**.

¡Practiquemos!

¡Practiquemos!

Refactoriza para eliminar argumentos usando aplicación parcial. const words = str => split(' ', str);


¡Practiquemos!

Refactoriza para eliminar argumentos aplicando parcialmente las funciones. const filterQs = xs => filter(x => match(/q/i, x), xs);


Dada la siguiente función:

js
const keepHighest = (x, y) => (x >= y ? x : y);

¡Practiquemos!

Refactoriza max para no referenciar argumentos, usando la función auxiliar keepHighest. const max = xs => reduce((acc, x) => (x >= acc ? x : acc), -Infinity, xs);