Explicado en 5 minutos: Mónadas en JavaScript simple

Dec 22 2022
A pesar de la foto de portada intimidante, intentaré explicar las mónadas usando la menor cantidad posible de matemáticas difíciles. Ninguna mención de la teoría de categorías, ningún Kleisli, ni siquiera Curry.
Foto de Dan Cristian Pădureț en Unsplash

A pesar de la foto de portada intimidante, intentaré explicar las mónadas usando la menor cantidad posible de matemáticas difíciles. Ninguna mención de la teoría de categorías, ningún Kleisli, ni siquiera Curry. Aunque habrá un NaN.

En palabras del gran Douglas Crockford:

Además de ser bueno y útil, también está maldito y la maldición de la mónada es que una vez que obtienes la epifanía, una vez que entiendes, "oh, eso es lo que es", pierdes la capacidad de explicárselo a los demás.

Bueno, tengo una teoría: esto se debe a que entre todos los excelentes artículos sobre este tema, que brindan una excelente explicación de la teoría matemática detrás de las mónadas y cómo deben implementarse (¡junto con ejemplos exquisitos!), una cosa falla:

¿Para qué los necesito realmente?

Antes de intentar averiguarlo, una observación. Hace algún tiempo, leí que realmente no deberías tratar de entender las mónadas basándote en algo que no esté firmemente basado en explicar las matemáticas detrás de ellas y su lugar en la teoría de categorías. Para aquellos interesados: hay algunos artículos excelentes que cubren ese tema, como este , o este , o el que introdujo las mónadas en la programación a partir de 1992 . Como realmente no quiero repetirlos aquí, léalos (o al menos escanéelos) ahora y vuelva aquí después (aunque creo que no es del todo necesario para entender lo que sucede a continuación).

OK, ahora lo tenemos fuera del camino: ¿qué es una mónada? Bueno, la forma más sencilla de decirlo es: es un envoltorio alrededor de algún valor. En términos de programación funcional, una mónada es un funtor , una estructura que permite aplicarle una función . Es una capa adicional de abstracción entre el valor y cualquier procesamiento por el que queramos someter el valor. Pero eso no nos dice nada.

Echemos un vistazo a la mónada más simple que puedas imaginar: una implementación de la Identitymónada:

const Identity = (value) => ({
  value: () => value,
  map: (fn) => Identity(fn(value)),
});

Veamos el ejemplo más simple posible:

const addTax = (value) => value * 1.1;

const cartValue = Identity(100);

const cartValueWithTax = cartValue
  .map(addTax) // At this point this is Identity(110)
  .value(); // Equal to 110 as a number

Cortesía de https://makeameme.org/meme/please-bear-with-tmby8v

La Identitymónada tiene, al igual que la identityfunción en muchas bibliotecas de programación funcional , una lógica bastante transparente. Es una mónada de marcador de posición, lo mismo que identityuna función de marcador de posición. Aplicar addTaxa nuestro valor funciona bien para el escenario dado anteriormente, pero pensemos en lo que sucedería si el valor de nuestro carrito fuera igual a undefined. Gracias a la maravillosa forma en que funciona JavaScript , obtendremos un archivo NaN.

Por lo general, para evitar esto, la forma más intuitiva sería agregar una verificación para el valor de la addTaxfunción. Este es un enfoque equivocado. Oscurece el propósito de la función y la hace menos pura. La addTaxfunción debe hacer solo una cosa y solo una cosa: addel Tax.

Bien, sabelotodo, podrías decir, ¿cómo debemos tener undefineden cuenta el valor? Bueno, para eso tenemos las mónadas. Queremos mantener la función pura. También queremos que nuestra lógica se comporte de manera diferente según el valor que le pasemos a la función. ¡ El mejor lugar para definir ese comportamiento es el funtor !

Veamos probablemente la mónada más popular que existe: Maybe. Esta podría ser una implementación básica (dejo los métodos habituales que puede encontrar en una mónada como ofpara mayor claridad):

const Maybe = (value) => ({
  value: () => value,
  map: (fn) => value === undefined || value === null 
    ? Maybe(null) 
    : Maybe(fn(value)),
});

const primitiveCart = 100;
const undefinedCart = undefined;
const nullishCart = null;

const maybeCart = Maybe(100);
const maybeUndefinedCart = Maybe(undefined);
const maybeNullishCart = Maybe(null);


addTax(primitiveCart); // boring, 110
addTax(undefinedCart); // NaN
addTax(nullishCart); // 0 - thanks, JavaScript!

maybeCart.map(addTax).value(); // 110
maybeUndefinedCart.map(addTax).value(); // null
maybeNullishCart.map(addTax).value(); // null

Esto, por supuesto, es solo un rasguño de la enorme riqueza de posibilidades que brinda el uso de mónadas. Digamos que queremos saludar a cada huésped que ingresa a nuestro hotel:

const greet = (name) => `Hello, ${name}!`;

Echemos un vistazo a un ejemplo más elemental: la Optionalmónada:

const Optional = (value) => ({
  value: () => value,
  orElse: (alternativeValue) => value 
    ? Optional(value) 
    : Optional(alternativeValue),
  map: (fn) => Optional(fn(value)),
});

const firstGuest = "John";
const anonymousGuest = undefined;

greet(firstGuest); // "Hello, John!"
greet(anonymousGuest); // "Hello, undefined" - this is a bit embarrasing

const OptionalWithStrangerFallback = (name) => Optional(name)
  .orElse("stranger");

const firstGuestOptional = OptionalWithStrangerFallback(firstGuest);
const anonymousGuestOptional = OptionalWithStrangerFallback(anonymousGuest);

// I really hope you know where this is going at this point

firstGuestOptional.map(greet).value(); "Hello, John!"
anonymousGuestOptional.map(greet).value(); "Hello, stranger!"

Realmente espero que esto aclare las cosas con las mónadas. Si no, ¡házmelo saber en los comentarios! Además, si desea leer más artículos sobre JavaScript, programación funcional, visión por computadora, arquitectura, geometría, memes, liderazgo técnico o cualquier combinación de los anteriores, ¡asegúrese de seguirme aquí o en LinkedIn !