Programación Funcional en JavaScript: La currificación

2297251722_b1b26de500_z

Nos habíamos quedado, en el post anterior, con la posibilidad de crear sistemas más modulares por medio de las tuberías. Estábamos mostrando técnicas con las que poder construir funciones puras más versátiles y flexibles por medio de entradas y salidas de un solo parámetro.

Vimos que, la solución de enviar una única salida por medio de tuplas, era una solución elegante y que ayudaba en nuestro propósito. Lo siguiente es encontrar una manera de poder crear funciones que solo permitan una única entrada (funciones unarias). Una forma de conseguir esto es por medio de lo que en lenguajes funcionales se conoce como ‘currificación’.

En este post, veremos en que se basa la currificación, cómo podemos implementarla en JavaScript y en qué nos puede ayudar. Parece interesante ¿Seguís conmigo?

¿Qué es la currificación?

La currificación es una técnica que convierte funciones multiparámetro en una secuencia de funciones unarias. Esto se consigue a través de suspender la ejecución de dicha función hasta que todos los parámetros necesarios han sido proporcionados.

En notación formal podemos definir esto de la siguiente forma:

curry(f) :: (a, b, c) -> f(a) -> f(b) -> f(c)

Como podemos ver, la notación formal nos está indicando que la currificación es un mapeo de función a función que deconstruye la entrada (a, b, c) en invocaciones de un simple parámetro.

Tenemos que tener cuidado de no confundirlo con la evaluación regular que es justamente lo contrario a la currificación. En JavaScript, aunque una función esté definida con multiparámetro, podemos ejecutar una función con un numero de parámetros menor. El resto de parámetros que no hayan sido especificados, serán instanciados a ‘undefined’.

Es decir, que si yo tengo  la siguiente función:

function f(a, b, c) { ··· }

Y yo la ejecuto así:

f(2);

JavaScript la ejecutará asi:

f(2, undefined, undefined);

Esta funcionalidad, que tan útil nos es en otros contextos, es la culpable de que en JavaScript no exista un soporte nativo de la currificación.

En otros lenguajes funcionales, cuando nosotros definimos la función anterior y solo indicamos un parámetro, se nos devuelve una función que permita incluir los otros dos nuevos parámetros. La currificación viene de serie.

Tendremos que volver a tirar de ingenio para conseguir esta funcionalidad.

¿Cómo podemos implementarla en JavaScript?

Nos basaremos en el uso de closures para conseguir una función que simule la currificación. Por ahora vamos a conseguir la currificación de funciones de 2 parámetros para simplificar el ejemplo. La función útil sería la siguiente:

function curry2(fn) {
    return function (firstParam) {
        return function (secondParam) {
            return fn(firstParam, secondParam);
        }
    }
}

Al final, nos queda algo bastante compacto y entendible. Es una función que nos convierte funciones que le pasamos de dos parámetros a un estilo ‘currificado’. El closure (o ámbito léxico) nos permite mantener el estado anterior. Gracias a esto ahora podemos hacer cosas así:

const fullNameCurry = curry2(function (name, lastname) {
    return [name, lastname];
});

const [name, lastname] = fullNameCurry('Jhon')('Doe');

fullNameCurry('Jhon'); // Function

Como vemos, está muy bien porque se introduce algo bueno y útil que son las evaluaciones parciales y de las cuales empezaremos a hablar largo y tendido en esta serie.

Lógicamente, en programas más complejos, necesitaremos ‘currificar’ funciones más complejas. Rambda y Lodash son dos librerías que proporcionan funcionalidad para currificación dinámica. En su documentación, podéis sacar más ejemplos de eso.

¿Qué usos podemos darle?

La currificación no solo nos ayuda en nuestro empleo por simplificar nuestras funciones, también nos ayuda en dos escenarios bastante buenos y muy conocidos en patrones de diseño. Esta técnica nos va a permitir:

  • Emular funciones factoría.
  • Implementar funciones plantilla.

Hablemos un poco de esto:

Emular funciones factoría

En lenguajes orientados a objetos, las interfaces nos ayudan a crear un contrato que queremos que ciertas clases cumplan. Esto favorece la comunicación entre el equipo y favorece a la legibilidad del código.

Por ejemplo, en C#, podriamos tener la siguiente interfaz:

interface IStudentStore {
    Student findStudent(String ssn);
}

Ahora podríamos tener dos clases que implementan esta interfaz. Cada clase se encargaría de obtener los estudiantes de fuentes de datos diferentes, pero para el programa, sería siendo lo mismo, el programa lo que necesita es obtener el estudiante. Esto es lo que se conoce como patrón método factoría. La implementación sería:

public class DBStoreStudent : IStudentStore {
    public Student findStudent(String ssn) {
         // No se muestra la instanciación de db. 
         // Se evita por simplificar
         return db.find(ssn);
    }
}

public class CacheStoreStudent : IStudentStore {
    public Student findStudent(String ssn) {
         // No se muestra la instanciación de caché. 
         // Se evita por simplificar
         return cache.find(ssn);
    }
}

Si ahora quisiéramos hacer uso de ello dependiendo de si tengo la caché activa o no:

IStudentStore store = hasCache ? new CacheStoreStudent() : new DBStoreStudent();
Student student = store.findStudent("444-444-444");

Es muy útil porque si mañana tenemos otro origen de datos en nuestra aplicación, la implementación sería más sencilla. La base de código cambiaría bastante poco y simplemente tendríamos que crear un nuevo tipo que herede de ese contrato. Como mi programa solo necesita que dado un ssn se devuelva un estudiante, no necesita saber de cómo está implementado. El contrato por tanto es importante.

En JavaScript y programación funcional no contamos con interfaces (en TypeScript si 😉 ), pero gracias a la currificación podemos implementarlo. Usemos Rambda para currificar las funciones de esta manera:

// findStudentFromDb :: DB -> (String -> Student)
const findStudentFromDb = R.curry(function (db, ssn) {
    return find(db, ssn);
});

// findStudentFromArray :: Array -> (String -> Student)
const findStudentFromArray = R.curry(function (array, ssn) {
    return array[ssn];
});

La clave está en (String -> String). Hemos creado una entrada y salida común (al final como en la interfaz), lo único que tenemos que indicar diferente es el origen de datos. Gracias a esto, ahora podemos hacer esto:

const findStudent = useDB ? findStudentFromDb(db) : findStudentFromArray(array)
const student = findStudent('444-444-444');

Hemos conseguido delegar la búsqueda en la función ‘findStudent’. El origen de datos es evaluado en la primera invocación.

No se si lo véis, pero esto nos será muy útil cuando queramos hacer test unitarios. Mockear datos puede ser más sencillo si podemos indicar el origen de datos de esta forma.

Implementar funciones plantilla

No os pasa en muchas ocasiones que usais una librería y dependiendo del contexto, necesitáis una configuración diferente, una forma de que se comporte. Al final tenemos desperdigadas nuestras configuraciones por la aplicación.

Sería bastante útil centralizar toda esta configuración. Sobre todo nos sería útil para, si el día de mañana queremos cambiar de librería, simplemente cambiando la parte centralizada, lo tendríamos.

En esto nos puede ayudar la currificación. Imaginemos que queremos centralizar la configuración para nuestra librería de logs favorita. Podríamos tener algo como esto:

const logger = R.curry2(function (appender, layout, name, level, message) {
    const appenders = {
         alert: new Log4js.JSAlertAppender(),
         console: new Log4js.BrowserConsoleAppender()
    };
    const layouts = {
         basic: new Log4js.BasicLayout(),
         json: new Log4js.JSONLayout(),
         xml: new Log4js.XMLLayout()
    };
    const appender = appenders[appender];
    appender.setLayout(layouts[layout];
    const logger = new Log4js.getLogger(name);
    logger.addAppender(appender);
    logger.log(level, message, null);
});

Ahora, gracias a esto, tenemos un logger que se encuentra totalmente aislado de la librería. Hemos creado una función plantilla que se va configurando según los parámetros que le vamos pasando. No me tengo que preocupar de la sintaxis de la propia librería.

Conclusión

La currificación es una técnica muy usada en programación funcional. Poder convertir funciones de varios parámetros en una sucesión de funciones con un único parámetro hace que consigamos sistemas más modulares y flexibles, lo que a la larga hará que mantengamos mejor nuestras aplicaciones y reutilicemos componentes.

La currificación es un primer paso para la creación de algoritmos parciales. La currificación nos ayuda mucho con conceptos de metaprogramación, software que se construye en tiempo de ejecución y que se comportará diferente dependiendo del contexto.

Vemos que JavaScript sufre mucho con conceptos muy puros de programación funcional. Ni nos ha dado soporte para el manejo de tuplas, ni para el manejo de la currificación de manera nativa. Es un lastre que tendremos que ver si nos compensa en el futuro. Lo que sí tenemos claro es que todas estas técnicas nos ayudan a solucionar problemas con JavaScript de una forma totalmente diferente y eso nos puede ayudar mucho.

Nos leemos 🙂

PD: Como siempre, este es el mapa conceptual seguido en el post:

la-currificacion

Anteriores posts de Programación Funcional en JavaScript:

Introducción

  1. La Programación Funcional en JavaScript
  2. Programación Funcional en JavaScript: Los Objetos
  3. Programación Funcional en JavaScript: Las funciones

El control de flujo funcional

  1. Programación Funcional en JavaScript: Los métodos funcionales
  2. Programación Funcional en JavaScript: La recursividad

La modularidad funcional

  1. Programación Funcional en JavaScript: La aridad y las tuplas
  2. Programación Funcional en JavaScript: La currificación

Imagen de portada | jnerin

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s