Programación Funcional en JavaScript: La mónada Maybe

Como vimos, los funtores nos abren un mundo de posibilidades. La estructura que presentamos en el post anterior nos ayudaba a la hora de tener entornos controlados y seguros que pudieran encadenarse y componerse.

Sin embargo, veremos que es solo el principio de lo que necesitábamos en realidad. La estructura de datos que presentamos tiene ciertos inconvenientes e incomodidades que vamos a solucionar con un nuevo patrón de diseño funcional llamado mónada.

Las mónadas suelen verse como un concepto complejo y demasiado abstracto. Esa leyenda esotérica es una de las razones por las que muchos desarrolladores acaban huyendo de la programación funcional. Su parte teórica es compleja, pero con usos prácticos creo que conseguiremos transmitir su gran importancia en el mundo funcional. Adelante:

Mejorando los funtores: las mónadas

La estructura que presentamos en el post anterior era muy simple, pero muy compacta y elegante a la vez. Sin embargo, tiene un pequeño problema y se debe a que trabajar con ella, en ciertos escenarios, puede ser algo incómodo.

Veamos es el siguiente ejemplo:

const findAccount = R.curry(function (db, idAccount) {
    return wrap(find(db, idAccount));
});

const getIban = function (account) {
    return wrap(account.fmap(R.prop('iban'));
}

const findIbanByAccountId = R.pipe(
    findAccount(DB('accounts-db')),
    getIban
);

findIbanByAccount('444-444-444'); // Wrapper(Wrapper('ES9121000418450200051332'))

Tenemos dos funciones que combinadas nos permiten obtener el el Iban de una cuenta de crédito personal. Las dos funciones deciden devolver el valor obtenido por medio de un ‘Wrapper’ para protegerlo.

El problema se debe a que estamos envolviendo nuestro valor con dos ‘Wrappers’. Obtener ahora el valor de esta ejecución se convierte en algo parecido a esto:

findIbanByAccount('444-444-444').map(R.identity).map(R.identity);

Mmm… bastante feo. Imaginemos que encadenamos y combinamos 8 funciones. Para obtener un valor, tenemos que ejecutar 8 veces la función ‘R.identity’. No parece una solución apropiada.

Bueno, para solucionar estos problemas existen las mónadas. Las mónadas son hijas hermanas de los funtores, lo que las diferencia es que saben delegar lógica especial cuando se gestionan ciertos casos de uso. 

Las mónada son una estructura, son un tipo de datos que nos permite simular sentencias y bloques imperativos de una manera funcional. Se encargan de evitar el control de flujo de una aplicación y en vez de ello, gestionan el flujo de los datos.

En este post explicaremos la primera de las tres mónadas que en la que profundizaremos en esta serie.

Aunque antes de esto, para entender las mónadas, necesitamos tener claro que una mónada se suele componer siempre de dos partes diferenciadas:

  • La mónada en sí: Proporciona una interfaz abstracta para una operación monádica.
  • El tipo monádico: Que es una implementación particular de esta interfaz abstracta.

Como iremos viendo, la implementación final de una mónada se parece mucho a la que vimos del funtor. Cada tipo de mónada intenta solucionar un problema en particular y esto hace que puedan tener una forma y una semántica diferente. Sin embargo, siempre encontrarás los siguientes elementos como identitarios de una mónada:

  • Un constructor: Crea tipos monádicos (similar al constructor del Wrapper).
  • Una ‘unit function’: Inserta un valor de cierto tipo dentro de la estructura monádica. A esta función se la suele llamar ‘of’.
  • Una ‘bind function’: Permite concatenar operaciones. En el functor se corresponde con la función ‘fmap’. Suele conocerse como ‘FlatMap’, pero usaremos ‘map’ para abreviar.
  • Una ‘join function’: Desmenuza las capas de una mónada.  Es una función que nos vendrá muy bien para evitar el ‘efecto cebolla’ a la hora de obtener un valor tras una cadena de funciones ejecutadas.

Si modificamos nuestro Wrapper obtendremos algo como esto:

class Wrapper {
   // un constructor
   constructor(value) {
      this._value = value;
   }
   
   // una 'unit function'
   static of(a) {
       return new Wrapper(a);
   } 

   // una 'bind function' (funtor)
   map(f) {
       return Wrapper.of(f(this._value));
   }

   // una 'join function'
   join() {
       if (!(this._value instanceof Wrapper)) {
           return this._value;
       }
       return this._value.join();
    }
 }

Si cambiamos el ejemplo anterior tendríamos algo como esto:

const findAccount = R.curry(function (db, idAccount) {
    return Wrapper.of(find(db, idAccount));
});

const getIban = function (account) {
    return Wrapper.of(account.map(R.prop('iban')); 
} 

const findIbanByAccountId = R.pipe( 
    findAccount(DB('accounts-db')), 
    getIban 
); 

findIbanByAccount('444-444-444').join(); 'ES9121000418450200051332'

La magia está en la maravilla de la función ‘join’. Da lo mismo lo interno y la de capas que recubran un valor que siempre se podrá obtener de una manera cómoda.

Da igual que yo haga esto:

Wrapper.of(Wrapper.of(Wrapper.of('Hello World'))).join(); // 'Hello World'

La cebolla siempre será desmenuzada.

La mónada Maybe

Además del problema con el ‘efecto cebolla’, los funtores no consiguieron solucionar el problema del manejo de errores.

Si, tenemos un entorno seguro donde ejecutar cosas, pero eso no significa que nuestro programa se encuentre libre de errores. Si por un casual nuestro código tuviese que gestionar alguna excepción, no sabría cómo hacerlo tal y como lo tenemos planteado.

Qué bueno sería que contasemos con una estructura libre de fallos que si en algún punto de esa ejecución no fuese como es debido, pudiese continuar hasta el final sin que el programa rompiera.

Ya vimos también, en el post anterior, que una manera de evitar las roturas de ejecución por medio de excepciones tan agresivas, era devolviendo ‘null’ y  delegando su posterior comprobación en funciones padre. Vimos que la solución ensuciaba demasiado el propósito y la lógica de nuestro programa, perdiendo foco en lo importante (demasiada programación defensiva).

Para solucionar este problema, existe la mónada ‘Maybe’. Esta mónada cuenta con dos tipos monádicos, ‘Just’ y ‘Nothing’. ‘Just’ es un envoltorio que será usado cuando las funciones que se ejecuten en nuestro entorno contengan un valor y ‘Nothing’ es un envoltorio que se será usado cuando las funciones que se ejecuten en nuestro entorno no contengan valor y devuelvan ‘null’.

La implementación de estas tres partes (la mónada ‘Maybe’ y los dos tipos monádicos ‘Just’ y ‘Nothing’) es la siguiente:

Mónada Maybe
class Maybe {
    static just(value) {
        return new Just(value);
    }

    static nothing() {
       return new Nothing();
    }

    get isJust() {
        return false;
    }

    get isNothing() {
        return false;
    }  

    static of(value) {
        return Maybe.just(value);
    }

    static fromNullable(value) {
       return value !== null ? Maybe.just(value) : Maybe.nothing();
    }
}
Tipo monádico Just
class Just extends Maybe {
    constructor(value) {
        super();
        this._value = value;
    }
    
    get value() {
        return this._value;
    }

    get isJust() {
        return true;
    }
    
    map(f) {
        return Maybe.fromNullable(f(this._value) || null);
    }

    getOrElse() {
        return this._value;   
    }
}
Tipo monádico Nothing
class Nothing extends Maybe {
    get value() {
        throw new TypeError('Can´t extract the value ot the Nothing');
    }

    get isNothing() {
        return true;
    }
    
    map() {
        return this;
    }

    getOrElse(other) {
        return other;   
    }
}

Las tres estructuras son bastante fáciles de seguir. Lo único relevante tiene que ver con que ‘Just’ alberga un valor (el que nosotros decidamos) y lo gestiona y ‘Nothing’ siempre alberga un ‘null’ y lo gestiona de tal manera que no se produzcan errores.

Gracias a esta estructura podemos evitar esa comprobación de ‘null’ tan horrenda del post anterior. Recordemosla un momento:

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

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

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

        return null;
    }
       
    return null;
}

Era una función que dado un cliente, nos devolvía el país de la dirección de su trabajo. Esta estructura podemos recrearla de forma funcional de la siguiente manera:

const getCountry = (client) => client.map(R.prop('job'))
                                     .map(R.prop('address'))
                                     .map(R.prop('country'))
                                     .getOrElse('Country not exist');

Esto funciona con una pequeña trampa y es que el cliente se encuentra dentro de una mónada ‘Maybe.’ Nosotros vamos ejecutando funciones sobre nuestro contexto. Si alguna genera un valor ‘null’, no ocurre nada ya que el tipo monádico ‘Nothing’ se va propagando hasta el final.

Puede que el ‘client’ venga vacío, o que no tenga un ‘job’. El ejemplo no provocará excepciones del sistema, no obtendremos el típico ‘TypeError’.

La función ‘getOrElse’ nos viene muy bien para poder mostrar un valor por defecto si no hemos encontrado lo que queríamos.

Veamos el ejemplo completo para mostrar cómo se recubre ‘client’;

const findClientBD = R.curry(function(db, clientId) {
    return Maybe.fromNullable(find(db, clientId));
};
const country = R.pipe(findClientBD('client-db'), getCountry);

country('123456');

El único ‘pero’ que tenemos con ‘Maybe’ es que manejar errores de esta manera es lo que yo personalmente denominaría: ‘dar un patadón hacia delante’. Es un mecanismo que nos da muy poca información de lo que ha pasado internamente en el programa. Se cumple la programación funcional a rajatabla, pero no es usable.

Esta mónada podemos usarla en muchos contexto, pero tendremos que tener en cuenta que saber la excepción que se ha provocado, saber la razón de por qué una función ha devuelto ‘null’, será más complicado.

Veremos, en el próximo post, que la mónada ‘Either’ es una evolución natural de ‘Maybe’ y sabremos cómo nos ayudará con este problema en particular.

Conclusión

Con pequeños cambios en la estructura base, conseguimos nuevas mónadas que nos ayudan a realizar casos de usos totalmente diferente. En este post hemos visto una de las más importantes, pero si navegais por Internet veréis, que al igual que nos pasaba con los combinadores, el número de posibilidades y de casos de uso es enorme.

Una vez que empecemos a desarrollar en entornos funcionales de una forma más asidua, veremos como las necesidades que tengamos pueden ser satisfechas por mónadas que desarrollemos nosotros mismos con pequeñas variaciones.

Si estáis muy acostumbrado a desarrollar con JavaScript, os estaréis dando cuenta que jQuery cumple con muchas de las decisiones arquitectónicas de la programación funcional. Las mónadas y los funtores, así como el encadenamiento de métodos y funciones, son conceptos muy comunes de la librería.

Esto hace que reflexionemos y veamos que todo lo que estamos aprendiendo no es en vano. Estamos entendiendo mejor las herramientas con las que llevamos años trabajando y estamos empezando a ser competentes en nuevas técnicas de diseños y análisis de problemas.

Nos leemos 🙂

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
  2. Programación Funcional en JavaScript: La mónada Maybe
  3. Programación Funcional en JavaScript: La mónada Either
  4. Programación Funcional en JavaScript: La mónada IO
Anuncios

7 comments

  1. Pingback: Programación Funcional en JavaScript: La mónada Either | el.abismo = de[null]
  2. Rubén Resino · enero 16

    Primero enhorabuena por los artículos. Y quería avisar que hay ejemplos con el código incompleto, quizás un error de copy & paste o una mala pasada del WordPress.

    Me gusta

    • jdonsan · enero 16

      Muchas gracias Rubén. Por mensajes como este sigo haciendo esta serie. Gracias enserio.

      En cuanto a lo del código que me comentas, los he revisado y los veo bien. ¿Sobre que dispositivo lees los post? Si es desde móvil hay que tener en cuenta que las partes de código tienen scroll horizontal. Espero que sea eso, un saludo 🙂

      Me gusta

      • EtnasSoft · enero 20

        Efectivamente, como comenta Rubén, hay errores en el código…

        Por ejemplo, aquí:
        const getIban = function (account) {
        return wrap(account.fmap(R.prop(‘iban
        }

        Se queda cortada la línea…

        Buen artículo!

        Me gusta

  3. Pingback: Programación Funcional en JavaScript: La mónada IO | el.abismo = de[null]
  4. Pingback: Programación Funcional en JavaScript: La evaluación perezosa | el.abismo = de[null]
  5. Pingback: Programación Funcional en JavaScript: La memoización | el.abismo = de[null]

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