Currificación

Primero, debemos recordar el concepto de Funciones Puras. Una función totalmente pura recibe uno y solo un argumento, y siempre devuelve un nuevo dato, consistente en su tipo con el argumento y su comportamiento es inmutable. También, recordemos que dijimos que no todas las funciones pueden o deben ser puras. Luego, definimos a la currificación es un método que nos ayuda a manejar múltiples argumentos y funciones puras haciendo algo llamado aplicación parcial.
Para empezar a hablar de currificación, debemos mencionar que recibe su nombre gracias a Haskell Curry, matemático norteamericano que contribuyó a la teoría del cálculo Lambda y a la lógica combinacional. De sus estudios sobre las inconsistencias del sistema de cálculo Lambda, deriva su propio sistema de representación funcional.

Este plantea que una función de n variables puede ser expresada como n-1 funciones contenidas recursivamente en sus closures dentro de una función que recibe una variable y retorna una de las n-1 funciones. Aquí veremos un ejemplo de 3 variables para evitar la complejidad en la definición matemática.

Definición

F(x, y, z) // Sea F una función de tres argumentos
Hx = ((y, z) -> F(x, y, z)) // Y sea Hx una función de dos argumentos que devuelve el mismo resultado que F
H = (x -> (y, z) ->F(x, y, z)) // Entonces podemos decir que H es una función que recibe x, y devuelve una función que recibe y, z, que devuelve lo mismo que F
H = (x -> (y -> (z -> F(x, y, z)))) // Entonces, aplico la definición recursivamente y obtengo esta expresión
x -> y -> z -> F(x, y, z) // Obtengo en limpio la identidad de H (⊙_⊙')

Una vez más, en código se simplifica la definición:

function F(a, b, c) {
  return a + b + c;
}
// F(1,2,3) == 6
const H = function (x) {
  return function (y) {
    return function (z) {
      return F(x,y,z)
    }
  }
}
// H(1)(2)(3) == 6
// o en ES6
const H = x => y => z => F(x,y,z)

Como vemos, la definición de H usando ES6 es casi idéntica a la definición matemática. Y tal vez encuentres verborrágico expresar una función de esta manera, pero te permite hacer algo que Curry definió como aplicación parcial.

Explicación

Si la función es pura y no tiene efectos secundarios, podemos remplazarla sin problemas, entonces vemos que una función

const suma = (a, b) => a + b

Si me encuentro con un llamado a suma(1,2) puedo remplazarlo por 3 en lugar de hacer el llamado a suma sin equivocarme, y de la misma manera puedo hacer el proceso inverso. Entonces, cuando llamo a H con el primer valor obtengo

H(1) === y => z => F(1, y, z)

Una función, que recibe un valor y, devuelve una función, que de nuevo, recibe un valor z y suma 1 + y + z. Volviendo a aplicar la definición, pero esta vez con 1 y 2 obtengo

H(1)(2) === z => F(1, 2, z) // una función que suma 1 + 2 + z

Como JS es un lenguaje donde las funciones son valores asignables y «ciudadanos de primera clase», puedo decir que

const sumar3 = H(1)(2)
sumar3(5) === 8
sumar3(2) === 5

Entonces, a partir de este proceso denominado currificación, puedo generar nuevas funciones, partiendo de una expresión genérica, que nos permite volver más especifico el comportamiento sin reescribir código.

Ejemplo

const dragonificar => x => y => z => `${z} es un dragón ${y} que escupe ${x}!`

const escupeFuego = dragonificar('fuego')
const escupeFuegoAzul = dragonificar('fuego azul')

escupeFuego('gigante')('Drogon') // Drogon es un dragón gigante que escupe fuego!
escupeFuego('grande')('Rhaegal') // Rhaegal es dragón grande que escupe fuego!

escupeFuegoAzul('grande')('Viserion') // Viserion es un dragón grande que escupe fuego azul!
escupeFuegoAzul('gigante')('Sindragosa') // Sindragosa es un dragón gigante que escupe fuego azul!

Este proceso podemos hacerlo nosotros, transformando nuestras funciones de múltiples argumentos, pero rápidamente puede volverse en un código tan lleno de paréntesis que harían llorar a un programador LISP. Para ayudarnos, existen muchas y muy buenas librerías funcionales, entre las cuales se destacan lodash, mori y rambda.

Todas estas, coinciden en el método curry( f ), que me devuelve, que me devuelve una función currificada, y con la posibilidad de hacer una aplicación «a mitad del teorema» de Curry, pudiendo hacer:

F(1)(2)(3)  // 6
F(1)(2,3)  // 6
F(1,2)(3)  // 6
F(1)()()(2,3)  // 6
F()()(1,2,3)  // 6

Comparación

Para empezar, JavaScript no es el lenguaje de programación funcional por excelencia, pero puede programarse con un comportamiento funcional. Los lenguajes funcionales, como LISP y Haskell hacen aplicaciones parciales y currificaciones de las funciones de manera implícita. Además, la evaluación de las funciones se hacen luego de ser interpretadas.

Por lo que en Haskell:

A B C 1

podrían ser tres argumentos de una función A, o A tomar como argumento el resultado de B con C y 1, o C ser una función de dos argumentos, hacer una aplicación parcial con 1, pasarlo a B que aplica parcialmente a A.

Conclusión

La aplicación parcial nos permite reutilizar nuestro código en patrones de transformaciones o reutilizar la transformación en patrones distintos. Conserva una similitud con las FOS ya que estas son pueden ser expresadas como una aplicación parcial de una iteración y una transformación de datos.

Seguimos expandiendo el ejemplo planteado en la parte anterior de la serie agregando funcionalidades. En esta ocasión, le aplicamos aplicación parcial para agregar más funcionalidad al ordenamiento.

Comparte este artículo

Entra en la discusión y deja tu comentario

Veces