Userò una descrizione indipendente dal linguaggio di monadi come questa, descrivendo prima i monoidi:
Un monoide è (approssimativamente) un insieme di funzioni che accettano un tipo come parametro e restituiscono lo stesso tipo.
Una monade è (approssimativamente) un insieme di funzioni che accettano un tipo di wrapper come parametro e restituiscono lo stesso tipo di wrapper.
Nota 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 tali operazioni, piuttosto che la classe contenuta.
Tradizionalmente, in un linguaggio OO useremmo una gerarchia di classi ed ereditarietà per fornire quella semantica. Così avremmo una Bird
classe con metodi takeOff()
, flyAround()
e land()
, e anatra avrebbero ereditato quelli.
Ma poi ci mettiamo nei guai con gli uccelli incapaci di volare, perché penguin.takeOff()
falliscono. Dobbiamo ricorrere al lancio e alla gestione delle eccezioni.
Inoltre, una volta che diciamo che Penguin è un Bird
, ci imbattiamo in problemi con eredità multipla, ad esempio se abbiamo anche una gerarchia di Swimmer
.
In sostanza stiamo provando a mettere le classi in categorie (con scuse per i ragazzi della teoria delle categorie) e definire la semantica per categoria piuttosto che nelle singole classi. Ma le monadi sembrano un meccanismo molto più chiaro per farlo rispetto alle gerarchie.
Quindi, in questo caso, avremmo una Flier<T>
monade come nell'esempio sopra:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... e non avremmo mai un'istanza a Flier<Penguin>
. Potremmo anche usare la tipizzazione statica per evitare che ciò accada, magari con un'interfaccia marcatore. O il controllo delle capacità di runtime per il salvataggio. Ma davvero, un programmatore non dovrebbe mai mettere un Pinguino in Flier, nello stesso senso in cui non dovrebbe mai dividere per zero.
Inoltre, è più generalmente applicabile. Un volantino non deve essere un uccello. Ad esempio Flier<Pterodactyl>
, oppure Flier<Squirrel>
, senza modificare la semantica di quei singoli tipi.
Una volta classificata la semantica in base a funzioni componibili su un contenitore - anziché con gerarchie di tipi - risolve i vecchi problemi con le classi che "tipo di fare, tipo di non" rientrano in una particolare gerarchia. Inoltre, consente facilmente e chiaramente più semantiche per una classe, come Flier<Duck>
pure Swimmer<Duck>
. Sembra che abbiamo lottato con una discrepanza di impedenza classificando il comportamento con le gerarchie di classi. Le monadi lo gestiscono elegantemente.
Quindi la mia domanda è, allo stesso modo in cui siamo arrivati a favorire la composizione sull'eredità, ha senso anche favorire le monadi sull'eredità?
(A proposito non ero sicuro se questo dovesse essere qui o in Comp Sci, ma questo sembra più un problema pratico di modellazione. Ma forse è meglio laggiù.)