Je vais utiliser une description indépendante du langage de monades comme celle-ci, décrivant d'abord les monoïdes:
Un monoïde est (grosso modo) un ensemble de fonctions qui prennent un certain type en paramètre et renvoient le même type.
Une monade est (à peu près) un ensemble de fonctions qui prennent un type d' encapsuleur comme paramètre et renvoie le même type d'encapsuleur.
Notez que ce sont des descriptions, pas des définitions. N'hésitez pas à attaquer cette description!
Donc, dans un langage OO, une monade permet des compositions d'opérations comme:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Notez que la monade définit et contrôle la sémantique de ces opérations, plutôt que la classe contenue.
Traditionnellement, dans un langage OO, nous utilisions une hiérarchie de classes et un héritage pour fournir ces sémantiques. Nous aurions donc une Bird
classe avec des méthodes takeOff()
, flyAround()
et land()
, et Duck en hériterait.
Mais alors nous avons des ennuis avec les oiseaux incapables de voler, car cela penguin.takeOff()
échoue. Nous devons recourir au lancer et à la manipulation d'exception.
De plus, une fois que nous disons que Penguin est un Bird
, nous rencontrons des problèmes d'héritage multiple, par exemple si nous avons également une hiérarchie de Swimmer
.
Essentiellement, nous essayons de classer les classes en catégories (avec des excuses aux gars de la théorie des catégories) et de définir la sémantique par catégorie plutôt qu'en classes individuelles. Mais les monades semblent être un mécanisme beaucoup plus clair pour ce faire que les hiérarchies.
Donc dans ce cas, nous aurions une Flier<T>
monade comme l'exemple ci-dessus:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... et nous n'instancierions jamais a Flier<Penguin>
. Nous pourrions même utiliser le typage statique pour empêcher cela, peut-être avec une interface de marqueur. Ou vérification des capacités d'exécution pour renflouer. Mais vraiment, un programmeur ne devrait jamais mettre un pingouin dans Flier, dans le même sens, il ne devrait jamais diviser par zéro.
En outre, il est plus généralement applicable. Un dépliant ne doit pas nécessairement être un oiseau. Par exemple Flier<Pterodactyl>
, ou Flier<Squirrel>
, sans changer la sémantique de ces types individuels.
Une fois que nous classons la sémantique par fonctions composables sur un conteneur - au lieu de hiérarchies de types - cela résout les anciens problèmes avec les classes qui "font, ne font pas" rentrent dans une hiérarchie particulière. Il permet également facilement et clairement plusieurs sémantiques pour une classe, comme Flier<Duck>
ainsi que Swimmer<Duck>
. Il semble que nous ayons lutté contre un décalage d'impédance en classant les comportements avec les hiérarchies de classes. Les monades le gèrent avec élégance.
Ma question est donc, de la même manière que nous en sommes venus à privilégier la composition à l'héritage, est-il également logique de privilégier les monades à l'héritage?
(BTW je ne savais pas si cela devrait être ici ou dans Comp Sci, mais cela ressemble plus à un problème de modélisation pratique. Mais c'est peut-être mieux là-bas.)