Userò una descrizione di monade simile alla lingua, come questa, descrivendo prima i monoidi:
A monoid is (roughly) a set of functions that take some type as a parameter and return the same type.
A monad is (roughly) a set of functions that take a wrapper type as a parameter and returns the same wrapper type.
Nota quelle che sono descrizioni, non definizioni. Sentiti libero di attaccare quella descrizione!
Quindi in un linguaggio OO, una monade consente composizioni operative come:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Si noti che la monade definisce e controlla la semantica di quelle operazioni, piuttosto che la classe contenuta.
Tradizionalmente, in un linguaggio OO useremmo una gerarchia di classi & eredità per fornire quella semantica. Avremmo quindi una classe Bird
con metodi takeOff()
, flyAround()
e land()
, e Duck li erediterebbe.
Ma poi ci troviamo nei guai con gli uccelli incapaci di volare, perché penguin.takeOff()
fallisce. Dobbiamo ricorrere al lancio e alla gestione delle eccezioni.
Inoltre, una volta che diciamo che Penguin è un Bird
, ci imbattiamo in problemi con ereditarietà multiple, ad esempio se abbiamo anche una gerarchia di Swimmer
.
Essenzialmente stiamo cercando di mettere le classi in categorie (con scuse ai ragazzi di Theory Theory), e definire la semantica per categoria piuttosto che in classi individuali. Ma le monadi sembrano un meccanismo molto più chiaro per farlo rispetto alle gerarchie.
Quindi, in questo caso, avremmo un Flier<T>
monad come nell'esempio sopra:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... e non creeremo mai un'istanza di Flier<Penguin>
. Potremmo anche usare la digitazione statica per evitare che ciò accada, magari con un'interfaccia marcatore. Oppure funzionalità di runtime: controllo del salvataggio. Ma in realtà, un programmatore non dovrebbe mai mettere un pinguino in un Flier, nello stesso senso in cui non dovrebbero mai dividere per zero.
Inoltre, è più generalmente applicabile. Un aliante non deve essere un uccello. Ad esempio Flier<Pterodactyl>
o Flier<Squirrel>
, senza modificare la semantica di quei singoli tipi.
Una volta che classifichiamo la semantica per funzioni componibili su un contenitore, anziché con le gerarchie di tipi, risolve i vecchi problemi con classi che "tipo di azione, tipo di non" si adattano a una particolare gerarchia. Anche facilmente & consente chiaramente più semantiche per una classe, come Flier<Duck>
e Swimmer<Duck>
. Sembra che abbiamo lottato con un disadattamento di impedenza classificando il comportamento con le gerarchie di classi. I Monad lo gestiscono con eleganza.
Quindi la mia domanda è, nello stesso modo in cui siamo arrivati a favorire la composizione rispetto all'ereditarietà, ha anche senso favorire le monadi sull'ereditarietà?
(BTW non ero sicuro se questo dovrebbe essere qui o in Comp Sci, ma questo sembra più un problema di modellazione pratica, ma forse è meglio lì.)