Programación Funcional en JavaScript: Los funtores

30277675424_a78d6e3a0d_c

Foto de Ewald Pieber

Empezamos una nueva fase en la serie funcional. Dejamos atrás la modularización de nuestro código y subimos el nivel para enfrentarnos a nuestros grandes compañeros: los patrones de diseño funcional.

Los patrones de diseño ayudan a manejar la complejidad de un sistema. Nos permiten crear una solución para un problema que puede darse en el desarrollo de software de manera recurrente. Al igual que la programación imperativa tiene sus patrones, la programación declarativa afronta sus problemas de una manera distinta. Es por ello que necesitamos nuevos patrones.

Una vez que dentro de la comunidad hemos visto que estos patrones no nos sirve para nada en un lenguaje como JavaScript, es momento para que pensemos que nos ofrecen los patrones funcionales. Hoy nos detendremos en un problema muy típico dentro del mundo real: El manejo de errores y excepciones.

Por medio de este post intentaré explicar como los funtores pueden ser un primer comienzo para este cometido. Adelante:

El problema de la gestión de errores

La programación funcional tiene fama de ser un estilo muy ligado a entornos científicos o matemáticos. No suele (o no solía) ser bien visto como un compañero de propósito general. Entre muchas de sus críticas se encuentra la dificultad para poder gestionar los errores.

La gestión de errores en lenguajes imperativos suele estar implementada con la siguiente estructura de flujo:

function myFunction() {
    try {
       // código a ejecutar 
    } catch (err) {
       // gestión del error
    }
}

Dentro del bloque ‘try’ envolvemos todo ese código que creemos que puede ser vulnerable a fallos. En la parte del ‘catch’ se gestiona el error. Cuando nuestro programa falla en tiempo de ejecución, los ‘catch’ son los encargados de recoger estas excepciones.

Que tengamos este mecanismo, no significa que en programación imperativa llenemos todo nuestro código de ‘tries‘, simplemente intentamos abarcar esas partes de código que sabemos que pueden ocasionarnos problemas, esas partes de código sobre las que no tenemos control, todas esas partes de código que puede generarnos efectos laterales.

El problema es que en programación funcional nos dedicamos a abstraer los flujos de la aplicación de las operaciones y un ‘try-catch’ es una estructura demasiado acoplada a estos fines.

Otro problema al que nos enfrentamos es a la forma en que se generan errores en un aplicativo imperativo. En estos estilos, cuando comprobamos que se ha generado un error, solemos disparar una excepción con la palabra reservada ‘throw’. De esta manera podemos salir de un flujo y romper el correcto funcionamiento de un programa para que un elemento padre pueda controlar el fallo.

Controlar errores de esta forma, por medio del disparo o elevación de errores, nos genera problemas con el paradigma:

  • El disparo de excepciones rompe nuestras cadenas de ejecución: Que yo pueda indicar cuando se producen estos errores, provocan que sea difícil controlar la ejecución.
  • Generar excepciones provoca efectos laterales: Si yo rompo la ejecución de una función de esta forma, dejo de tener funciones puras. No siempre existirá una entrada y una salida, por tanto no cumplimos con la transparencia referencial.
  • Delegamos demasiada responsabilidad en la función padre: esta función padre tiene que encargarse o no de gestionar el error y por tanto saber demasiado del comportamiento interno de la función hija.
  • La gestión de errores encadenada (que una excepción provoque otra excepción) se convierte en un proceso complejo.

La comprobación de nulos

Lógicamente, usar excepciones es necesario y en muchos casos no nos quedará más remedio que hacer uso de ellas. Por ejemplo, puede que estemos desarrollando una librería y que no tengamos otra manera de crear excepciones que por medio de estos mecanismos. Incluso puede que usemos librerías de terceros que disparen excepciones de una forma imperativa.

Es por ello que debemos defender nuestro código de situaciones en las que se puede producir un error. Una excepción muy recurrente en JavaScript es ‘TypeError’: Queremos ejecutar una función y derepente ‘pum’: el objeto undefined no tiene definido este método.

Si estamos diciendo que deberíamos evitar disparar excepciones de esta forma tan agresiva en modelos funcionales, no sería mala idea que, cuando una función no esté funcionando de una forma correcta devolviese un ‘null’. Es una forma elegante de no romper nuestra cadena de ejecución y nos ayuda a no sufrir por los efectos laterales.

Sin embargo, sigue siendo un modelo poco útil. Veamos un ejemplo. Necesitamos obtener el país de la dirección del trabajo de un cliente. Podríamos crear una función como la siguiente:

function getCountry(client) {
    let job = client.getJob();

    if (job !== null) {
        let address = job.getAddress();

        if (address !== null) {
             return address.getCountry();
        }

        return null;
    }
       
    return null;
}

Aunque la función se podría refactorizar mejor, el ejemplo nos pone de manifiesto que esta solución no es todo lo adecuada que podría parecer. Fijémonos que para hacer algo tan sencillo, hemos tenido que escribir un elevado número de líneas de código. Tanta programación defensiva lo único que hace es desviarnos de nuestro cometido que debería ser obtener el ‘país’ necesario.

No solo estamos postergando el fallo del programa, sino que por el camino estamos perdiendo una gran cantidad de información útil. ¿Cúal es el elemento que ha empezado a devolver null? ¿Donde podría evitar el fallo? ¿Como lo corrijo? La solución del ‘check-null’ podrá servirnos en pequeños casos, pero en un entorno funcional real no nos es útil. Necesitamos algo más potente.

Los funtores

Al final la idea del ‘try-catch’ no es descabellada. La idea nace de tener un entorno controlado donde poder ejecutar algo del que no tenemos fe de que vaya a funcionar correctamente.

El ‘try-catch’ como estructura no nos sirve, pero ¿y si nos quedáramos con la idea e intentásemos envolver nuestro contexto para tener un entorno seguro donde ejecutar ‘cosas’?

A la gente funcional se le ha ocurrido la genial idea de hacer uso de una estructura de datos parecida a esta:

class Wrapper {
    constructor(value) {
        this._value = value;
    }

    map(fn) {
        return fn(this._value);
    }

    toString() {
       return `Wrapper [${this._value}]`; 
    }
}

const wrap = (val) => new Wrapper(val);

Es una solución bastante simple. Se trata de crear una clase envoltorio que se encargue de hospedar a nuestro estado u objeto. De esta forma yo controlo que sí y que no se puede ejecutar sobre estos datos.

Si nos damos cuenta, no existe forma de obtener los datos de la clase – Vale, es cierto que esto es JavaScript y que podría hacerse uso de ‘_value’, pero intentemos no hacerlo) Hemos creado un espacio, un contexto donde el dato pueda estar seguro.

Lo siguiente en lo que hay que fijarse es en el método ‘map’. Este método es una puerta de entrada para que otros puedan ejecutar funciones sobre mi entorno seguro. De esta forma, por ejemplo, yo podría manipular el dato u obtenerlo de una forma controlada.

La idea parece algo obvia, pero si lo adornamos con un poco más de código dentro del método ‘map’ podría crear precondiciones y postcondiciones para saber si la función que se necesita ejecutar en mi entorno es segura o no.

Bueno, es un sistema muy compacto y robusto, a mi me gusta. Cumple todas las reglas funcionales – posibilidad de encadenar, combinar y componer – y es un buen comienzo para que creamos sistemas que sean tolerante a fallos. 

Pongamos un pequeño ejemplo para ver su potencia. Instanciamos un envoltorio de un texto y juguemos un poco con él:

const stringWrapper = wrap('Hello World');

stringWrapper.map(R.identity); // Hello World
stringWrapper.map(R.toUpper); // HELLO WORLD
stringWrapper.map(console.log);

La función ‘map’ tiene un pequeño problema y es que cuando terminamos de ejecutar la función que se pasa como parámetro, nos devuelve un valor. Esto hace que perdamos seguridad. Es como si después de tener a nuestro gato (valor) tanto tiempo en resguardo en nuestra casa (envoltorio) llegara nuestro vecino (función que ejecuta el ‘map’) y se lo llevase en su primera visita.

Podemos solucionar esto con el siguiente método:

Wrapper.prototype.fmap = function (fn) {
    return wrap(fn(this._value));
}

De esta forma, cuando ejecutamos una función dentro de nuestro entorno seguro, devolvemos un nuevo envoltorio que contiene en su interior el nuevo valor calculado. De esta forma seguimos teniendo control de nuestro contexto, pero en una nueva caja segura. Además, ‘fmap’ nos ayuda en el encadenamiento de funciones.

Ahora podemos hacer cosas como esta:

const numberWrap = wrap(2);
const sum5 = num => num + 5;

numberWrap.fmap(sum5); // return Wrapper(7)

Y claro, ahora ya podríamos hacer cosas como esta:

numberWrap.fmap(sum5).fmap(sum5).fmap(sum5); // return Wrapper(17)

Esta nueva alternativa es lo que en programación funcional conocemos como funtor. Un funtor es un patrón de diseño funcional que nos ayuda a envolver entornos seguros de una forma cómoda y sencilla. La forma normal de definir un funtor es:

fmap :: (A -> B) -> Wrapper(A) -> Wrapper(B)

Los funtores son indispensables para este paradigma, lo veremos a lo largo de la serie con casos más especializados e incluso has podido ver dos funtores a lo largo de la serie 🙂

Si os acordáis, en este post hablábamos de los métodos funcionales que venían de serie para los array en ES5 y uno de ellos era el método ‘map’. Es un funtor especializado en arreglos. Su forma normal es :

map :: (A -> B) -> Array(A) -> Array(B)

¿Os suena? Se parece bastante a la forma normal del funtor. Dado un array y una función que convierte elementos del array A en elementos del array B conseguimos un nuevo array B.

Mismo caso con el post sobre composición: ‘pipe’ y ‘compose’ son dos funtores:

compose :: (B -> C) -> (A -> B) -> (A -> C)

Los hemos estado usando todo el rato y nos nos habíamos dado cuenta 🙂

Conclusión

Si, tenemos un entorno más seguro, pero no tenemos nada todavía. Los funtores son el inicio para entender un patrón mucho má útil para tener entornos tolerante a fallos: las mónadas.

En el siguiente post, veremos los problemas que presenta los funtores como estructura de datos e intentaremos arrojar luz sobre como crear aplicaciones del mundo real con entornos funcionales.

PD: Rubén Fernández me recomendó un recurso sobre funtores y mónadas muy visual y que comparto con todos vosotros por si no lo conocéis. Gracias Rubén.

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
  3. Programación Funcional en JavaScript: La composición
  4. Programación Funcional en JavaScript: Los combinadores

Patrones funcionales

  1. Programación Funcional en JavaScript: Los funtores

Nos leemos 🙂

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