Funciones parciales y curry en JavaScript

Curry, o aplicación parcial, es una de las tecnicas de programación funcional que mas confusión puede causar entre las personas que estan acostumbradas a programar en JavaScript de modo tradicional (imperativo). Sin embargo, cuando se aplica correctamente puede llegar a hacer de su codigo una verdadera obra de arte, y minimizar la duplicidad de codigo, haciendo que sea mas legible y reutilizable.

Mas legible y mas flexible.

Una de las ventajas que se promocionan entre los que usamos JavaScript como lenguaje funcional, es que es mas corto, mas estricto y que va directo al punto en el menor numero de lineas posibles, y con menos repetición. A veces esto puede ir a expensas de la legibilidad; hasta que se haya familiarizado con la forma en la que funciona la programacion funciónal, el codigo escrito de esta manera puede llegar a ser mas dificil de leer y entender en un principio.

Si ya ha encontrado el termino “Curry” en libros o en articulos anteriormente, pero nunca supo bien lo que significaba, sepa que es perdonado por pensar en el “curry” como algo exotico, una tecnica “picante” de la cual usted no tiene porque preocuparse. Pero curry es en realidad un concepto muy simple pero poderoso, y aborda unos problemas que nos son muy familiares cuando tratamos con los argumentos de las funciones, mientras que habre una gran gama de opciones muy flexibles para el desarrollador.

¿Pero que es curring?

En pocas palabras, curring es una manera de contruir funciónes que permite la aplicación parcial de argumentos en la llamada a la función. Lo que esto significa es que usted puede pasar todos los argumentos que una funcion esta esperando y obtener el resultado, o pasar un subconjunto de esos argumentos y obtener una copia de la función que esta esperando para el resto de los argumentos. Es realmente asi de simple.

El curry es algo elemental en lenguajes como Haskell y Scala, los cuales se construyen en torno a conceptos funcionales. JavaScript en cambio tiene capacidades funcionales, pero la currificación no se construye por defecto en este lenguaje (Al menos no en las versiones actuales). Pero como ya sabemos algunos trucos funcionales, podemos hacer que curring funcione para nosotros tambien en JavaScript.

Para que nos demos una idea de como esto podria funcionar, vamos a crear nuestra primera función curry en javascript, utilizando una sintaxis familiar para construir la funcionalidad currificada que necesitamos. Como ejemplo, imaginemos una simple función que saluda a alguien por su nombre. Todos sabemos como crear una simple función saludar que toma un nombre y un saludo, y muestra el nombre y el saludo en la consola:

1
2
3
4
var greet = function(greeting, name) {
console.log(greeting + ", " + name);
};
greet("Hello", "Heidi"); //"Hello, Heidi"

Esta función requiere que tanto el nombre como el saludo se pasen como argumentos para poder funcionar correctamente. Pero podriamos reescribir esta funcion utilizando una currificación sencilla anidada, de modo que la función básica solo requiera un saludo, y retorne otra función que tome como argumento el nombre de la persona a la que queremos saludar.

Nuestro primer curry.

1
2
3
4
5
var greetCurried = function(greeting) {
return function(name) {
console.log(greeting + ", " + name);
};
};

Este pequeño cambio en la forma de escribir la función, nos permite crear una nueva función para cualquier tipo de saludo, y pasar a esa nueva función, el nombre de la persona a quien queremos saludar:

1
2
3
var greetHello = greetCurried("Hello");
greetHello("Heidi"); //"Hello, Heidi"
greetHello("Eddie"); //"Hello, Eddie"

También podemos llamar a nuestra función currificada directamente, simplemente pasando cada uno de los parámetros en un conjunto separado de parentesís, uno detras del otro:

1
greetCurried("Hi there")("Howard"); //"Hi there, Howard"

¿Por qué no probar esto en nuestro navegador?
Ejemplo en JsBin

Currifica todas las cosas!

Lo interesante es, ahora que hemos aprendido a modificar nuestras funciónes para utilizar este enfoque con los argumentos, que podemos hacer esto con tantos argumentos como deseemos:

1
2
3
4
5
6
7
8
9
var greetDeeplyCurried = function(greeting) {
return function(separator) {
return function(emphasis) {
return function(name) {
console.log(greeting + separator + name + emphasis);
};
};
};
};

Tenemos la misma flexibilidad, con 4 argumentos como antes con 2. No importa que tan lejos llegue la anidación, podemos crear nuevas funciónes personalizadas para saludar a tantas personas como queramos en tantas formas como deseemos:

1
2
3
var greetAwkwardly = greetDeeplyCurried("Hello")("...")("?");
greetAwkwardly("Heidi"); //"Hello...Heidi?"
greetAwkwardly("Eddie"); //"Hello...Eddie?"

Lo que es más, podemos pasar tantos parametros como deseemos al crear variaciónes personalizadas en nuestra funcion currificada original, tambien podemos crear nuevas funciónes que son capaces de tomar el numero de parametros restantes, cada argumento pasado por separado en su propio conjunto de parentesis:

1
2
3
var sayHello = greetDeeplyCurried("Hello")(", ");
sayHello(".")("Heidi"); //"Hello, Heidi."
sayHello(".")("Eddie"); //"Hello, Eddie."

Y podemos definir variaciónes subordinadas asi de facil:

1
2
3
var askHello = sayHello("?");
askHello("Heidi"); //"Hello, Heidi?"
askHello("Eddie"); //"Hello, Eddie?"

Ejemplo en JsBin

Currificando funciónes tradicionales.

Se puede ver lo poderoso que es este enfoque, especialmente cuando usted necesita crear una gran cantidad de funciones personalizadas muy detalladas. El unico problema es la syntaxis. A medida que se construyen estas funciones currificadas, usted necesita mantener un gran numero de retorno de funciónes anidadas, y llamarlas con nuevas funciones que requieren multiples sets de parentesis, conteniendo cada uno su propio argumento aislado. Esto puede causar problemas.

Para hacer frente a este problema, un enfoque es crear una función curry rapidamente, y que tome el nombre de una función existente que haya sido escrita sin todos esos horrendos retornos anidados. Una función curry necesitaria obtener todos los argumentos de la funcion que desea currificar, y usarlos para devolver una función curry de la función original.

1
2
3
4
5
6
7
8
var curryIt = function(uncurried) {
var parameters = Array.prototype.slice.call(arguments, 1);
return function() {
return uncurried.apply(this, parameters.concat(
Array.prototype.slice.call(arguments, 0)
));
};
};

Para utilizar esto, le pasamos el nombre de una función que toma cualquier numero de argumentos, junto con los argumentos que deseamos rellenar previamente. Lo que retornara una función que espera el numero de argumentos restantes:

1
2
3
4
5
6
var greeter = function(greeting, separator, emphasis, name) {
console.log(greeting + separator + name + emphasis);
};
var greetHello = curryIt(greeter, "Hello", ", ", ".");
greetHello("Heidi"); //"Hello, Heidi."
greetHello("Eddie"); //"Hello, Eddie."

Y al igual que antes, no estamos limitados en el numero de argumentos que podemos utlizar cuando construimos funciónes derivadas de nuestra función curry original:

1
2
var greetGoodbye = curryIt(greeter, "Goodbye", ", ");
greetGoodbye(".", "Joe"); //"Goodbye, Joe."

Ejemplo en JsBin

Tomando en serio la currificación

Nuestra pequeña función de currificación, no puede manejar todos los casos, tales como parámetros faltantes u opcionales, pero hace un trabajo razonable, siempre y cuando nos mantengamos estrictos sobre la sintaxis para el paso de los argumentos.

Algunas librerias funcionales para JavaScript como ramda tienen funciónes currificadas mas flexibles que son capaces de romper los parametros requeridos para una función, y permiten pasarlas en forma individual o en grupos para crear variaciones personalizadas de la función currificada. Si desea usar ampliamente curring, este es probablemente el mejor camino a seguir.

Independientemente de como elija agregar la currificacion en su codigo, ya sea que desee utilizar parentesis anidados o si prefiere incluir una función curry mas robusta, ramda viene con una convención de momenclatura coherente para sus funciónes curry que ayuda a que el codigo sea mas legible. Cada variacion derivada de una función currificada debe de tener un nombre que deje en claro como se comporta, y que argumentos esta esperando.

Orden de los argumentos.

Una cosa que es importante tener en cuenta a la hora de diseñar una función a la cual vamos a querer currificar, es el orden de los argumentos. Aqui hay una ley casi inexorable que es poner siempre los datos como ultimo parametro de la función. Imaginemos por ejemplo que tenemos que generar una gran cantidad de funciónes para el trabajo con strings, en este ejemplo utilizaremos ramda para apoyarnos en su función curry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var curry = require("ramda").curry;
var match = curry(function(what, str) {
return str.match(what);
});
var replace = curry(function(what, replacement, str) {
return str.replace(what, replacement);
});
var filter = curry(function(f, ary) {
return ary.filter(f);
});
var map = curry(function(f, ary) {
return ary.map(f);
});

Si nos fijamos, hemos posicionado de forma estrategica los parametros en las funciónes, siempre el ultimo argumento es el dato sobre el cual vamos a trabajar. Veamos como podemos usar estas funciones currificadas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
match(/\s+/g, "hello world");
// [ ' ' ]
match(/\s+/g)("hello world");
// [ ' ' ]
var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }
hasSpaces("hello world");
// [ ' ' ]
hasSpaces("spaceless");
// null
filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]
var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }
findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]
var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }
var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }
censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'

Si nos damos cuenta, de 4 funciónes currificadas que teniamos hemos generado una gran cantidad de otras utiles funciónes casi de la nada, aprobechando mucho mejor el codigo y sin tener que duplicar codigo.

Conclusión

La currificación es una tecnica increiblemente util de la programacion funcional que podemos usar facilmente en JavaScript. Permite generar una libreria de pequeñas y utiles funciónes faciles de configurar que se comportan de manera consistente. Son rapidas de usar, y pueden entenderse facilmente al leer su codigo. Aderir la tecnica de currificación a su dia a dia, fomentara el uso de las funciones aplicadas parcialemente a trabes de su codigo, evitando mucha repetición, y puede ayudar a conseguir mejores habitos sobre el nombramiento de las funciones y a hacer frente a los argumentos de las funciones.

Comentarios