Spiegato in 5 minuti: monadi in semplice JavaScript

Dec 22 2022
Nonostante la foto di copertina intimidatoria, cercherò di spiegare le monadi usando meno matematica possibile. Nessun accenno alla teoria delle categorie, nessun Kleisli, nemmeno Curry.
Foto di Dan Cristian Pădureț su Unsplash

Nonostante la foto di copertina intimidatoria, cercherò di spiegare le monadi usando meno matematica possibile. Nessun accenno alla teoria delle categorie, nessun Kleisli, nemmeno Curry. Anche se ci sarà un NaN.

Nelle parole del grande Douglas Crockford:

Oltre ad essere buono e utile, è anche maledetto e la maledizione della monade è che una volta che ottieni l'epifania, una volta che capisci - "oh, ecco cos'è" - perdi la capacità di spiegarlo a chiunque altro.

Bene, ho una teoria - questo perché tra tutti gli eccellenti articoli su questo argomento, che forniscono un'ottima spiegazione della teoria matematica dietro le monadi e di come dovrebbero essere implementate (insieme a esempi squisiti!), una cosa non va:

A cosa mi servono effettivamente quelli?

Prima di provare a scoprirlo, un'osservazione. Qualche tempo fa, ho letto che non dovresti davvero cercare di capire le monadi basandoti su qualcosa che non sia fermamente basato sulla spiegazione della matematica dietro di loro e sul loro posto nella Teoria delle Categorie. Per chi fosse interessato: ci sono alcuni articoli eccellenti che trattano quell'argomento, come questo , o questo , o quello che ha introdotto le monadi nella programmazione dal 1992 . Dal momento che non voglio davvero ripeterli qui, per favore leggeteli (o almeno scansionateli) ora e tornate qui dopo (anche se credo che non sia del tutto necessario capire cosa succede dopo).

OK, ora l'abbiamo tolto di mezzo: cos'è una monade? Bene, il modo più semplice per dirlo è: è un involucro attorno a un valore. In termini di programmazione funzionale, una monade è un funtore , una struttura che permette di applicarvi una funzione . È un ulteriore livello di astrazione tra il valore e qualsiasi elaborazione a cui vogliamo sottoporre il valore. Ma questo non ci dice niente.

Diamo un'occhiata alla monade più semplice che potresti immaginare: un'implementazione della Identitymonade:

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

Diamo un'occhiata all'esempio più semplice possibile:

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

Per gentile concessione di https://makeameme.org/meme/please-bear-with-tmby8v

La Identitymonade ha, proprio come la identityfunzione in molte librerie di programmazione funzionale , una logica abbastanza trasparente. È una monade segnaposto, come identityuna funzione segnaposto. L'applicazione addTaxal nostro valore funziona bene per lo scenario sopra indicato, ma pensiamo a cosa accadrebbe se il valore del nostro carrello fosse uguale a undefined. Grazie al meraviglioso modo in cui funziona JavaScript , otterremo un file NaN.

Di solito, per aggirare questo problema, il modo più intuitivo sarebbe aggiungere un controllo per il valore alla addTaxfunzione. Questo è un approccio sbagliato. Oscura lo scopo della funzione e lo rende meno puro. La addTaxfunzione dovrebbe fare solo una cosa e solo una cosa: addil file Tax.

OK, saggio - potresti dire - come dovremmo tenere undefinedconto del valore? Bene, è per questo che abbiamo le monadi. Vogliamo mantenere la funzione pura. Vogliamo anche che la nostra logica si comporti diversamente in base al valore che passiamo alla funzione. Il posto migliore per definire quel comportamento è il funtore !

Diamo un'occhiata alla monade probabilmente più popolare che ci sia: Maybe. Questa potrebbe essere un'implementazione di base (lascio i soliti metodi che potresti incontrare in una monade come ofper chiarezza):

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

Questo, ovviamente, è solo un graffio dell'enorme ricchezza di possibilità che l'uso delle monadi offre. Diciamo che vogliamo salutare ogni ospite che entra nel nostro hotel:

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

Diamo un'occhiata solo a un altro esempio elementare: la Optionalmonade:

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!"

Spero davvero che questo chiarisca le cose con le monadi. In caso contrario, fatemelo sapere nei commenti! Inoltre, se desideri leggere altri articoli su JavaScript, programmazione funzionale, visione artificiale, architettura, geometria, meme, leadership tecnica o qualsiasi combinazione di quanto sopra, assicurati di seguirmi qui o su LinkedIn !