Będę używał agnostycznego opisu takich monad, opisując najpierw monoidy:
Monoid jest (w przybliżeniu) zestaw funkcji, które mają jakiś rodzaj jako parametr i zwraca ten sam typ.
Monada jest (w przybliżeniu) zestaw funkcji, które wymagają owinięcia typu jako parametr i zwraca samego rodzaju owijki.
Zauważ, że są to opisy, a nie definicje. Zapraszam do ataku na ten opis!
Tak więc w języku OO monada pozwala na takie kompozycje operacji, jak:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Zauważ, że monada definiuje i kontroluje semantykę tych operacji, a nie klasę zawartą.
Tradycyjnie w języku OO używamy hierarchii klas i dziedziczenia, aby zapewnić semantykę. Tak, że mamy Bird
klasę z metodami takeOff()
, flyAround()
a land()
, a Duck odziedziczy tych.
Ale potem wpadamy w kłopoty z nielotnymi ptakami, ponieważ penguin.takeOff()
zawodzą. Musimy uciekać się do rzucania wyjątków i obsługi.
Ponadto, gdy powiemy, że Pingwin jest a Bird
, napotkamy problemy z wielokrotnym dziedziczeniem, na przykład jeśli mamy również hierarchię Swimmer
.
Zasadniczo staramy się podzielić klasy na kategorie (z przeprosinami dla osób z Teorii Kategorii) i zdefiniować semantykę według kategorii, a nie poszczególnych klas. Monady wydają się jednak znacznie bardziej przejrzystym mechanizmem niż hierarchie.
W takim przypadku mielibyśmy Flier<T>
monadę jak w powyższym przykładzie:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... i nigdy nie będzie instancji Flier<Penguin>
. Możemy nawet użyć pisania statycznego, aby temu zapobiec, być może z interfejsem znacznika. Lub sprawdzanie zdolności środowiska wykonawczego w celu ratowania. Ale tak naprawdę, programista nigdy nie powinien umieszczać pingwina we Flier, w tym samym sensie, że nigdy nie powinien dzielić przez zero.
Ponadto ma to bardziej ogólne zastosowanie. Lotnik nie musi być ptakiem. Na przykład Flier<Pterodactyl>
lub Flier<Squirrel>
bez zmiany semantyki tych poszczególnych typów.
Gdy klasyfikujemy semantykę według funkcji składanych w kontenerze - zamiast hierarchii typów - rozwiązuje stare problemy z klasami, które „w pewnym sensie robią, w pewnym sensie nie pasują” do określonej hierarchii. Pozwala również łatwo i wyraźnie dopuszczać wiele semantyki dla klasy, jak Flier<Duck>
również Swimmer<Duck>
. Wygląda na to, że walczyliśmy z niedopasowaniem impedancji, klasyfikując zachowanie za pomocą hierarchii klas. Monady radzą sobie z tym elegancko.
Tak więc moje pytanie brzmi: w taki sam sposób, w jaki preferujemy kompozycję nad dziedziczeniem, czy sens ma również faworyzowanie monad nad dziedziczeniem?
(BTW, nie byłem pewien, czy to powinno być tutaj, czy w Comp Sci, ale wydaje się to bardziej praktycznym problemem modelowania. Ale może tam jest lepiej.)