Ich werde eine sprachunabhängige Beschreibung von Monaden wie diese verwenden und zuerst Monoide beschreiben:
Ein Monoid ist (ungefähr) eine Menge von Funktionen, die einen bestimmten Typ als Parameter annehmen und denselben Typ zurückgeben.
Eine Monade ist (ungefähr) eine Reihe von Funktionen, die einen Wrapper- Typ als Parameter verwenden und denselben Wrapper-Typ zurückgeben.
Beachten Sie, dass dies Beschreibungen und keine Definitionen sind. Fühlen Sie sich frei, diese Beschreibung anzugreifen!
In einer OO-Sprache erlaubt eine Monade Operationskompositionen wie:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Beachten Sie, dass die Monade die Semantik dieser Operationen und nicht die enthaltene Klasse definiert und steuert.
Traditionell verwenden wir in einer OO-Sprache eine Klassenhierarchie und -vererbung, um diese Semantik bereitzustellen. So hätten wir eine haben Bird
Klasse mit Methoden takeOff()
, flyAround()
und land()
, und Ente würden diejenigen erben.
Aber dann bekommen wir Ärger mit flugunfähigen Vögeln, weil es penguin.takeOff()
versagt. Wir müssen auf das Ausnahmewerfen und -handling zurückgreifen.
Bird
Wenn wir einmal sagen, dass Pinguin ein Pinguin ist , haben wir Probleme mit der Mehrfachvererbung, zum Beispiel wenn wir auch eine Hierarchie von Pinguinen haben Swimmer
.
Im Wesentlichen versuchen wir, Klassen in Kategorien einzuteilen (mit Entschuldigung an die Kategorietheoretiker) und die Semantik eher nach Kategorien als nach einzelnen Klassen zu definieren. Aber Monaden scheinen ein viel klarerer Mechanismus dafür zu sein als Hierarchien.
In diesem Fall hätten wir also eine Flier<T>
Monade wie das obige Beispiel:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... und wir würden niemals a instanziieren Flier<Penguin>
. Wir könnten sogar statische Typisierung verwenden, um dies zu verhindern, möglicherweise mit einer Markierungsschnittstelle. Oder Laufzeitfähigkeitsprüfung zur Rettung. Aber wirklich, ein Programmierer sollte niemals einen Pinguin in Flier stecken, in dem gleichen Sinne, in dem sie niemals durch Null teilen sollten.
Es ist auch allgemeiner anwendbar. Ein Flieger muss kein Vogel sein. Zum Beispiel Flier<Pterodactyl>
oder Flier<Squirrel>
, ohne die Semantik dieser einzelnen Typen zu ändern.
Sobald wir die Semantik durch zusammensetzbare Funktionen in einem Container klassifizieren - anstelle von Typhierarchien - werden die alten Probleme mit Klassen behoben, die "irgendwie tun, irgendwie nicht" in eine bestimmte Hierarchie passen. Es erlaubt auch leicht und klar mehrere Semantiken für eine Klasse, wie Flier<Duck>
auch Swimmer<Duck>
. Es scheint, als hätten wir mit einer Impedanzinkongruenz zu kämpfen, indem wir das Verhalten anhand von Klassenhierarchien klassifizierten. Monaden gehen elegant damit um.
Meine Frage ist also, in der gleichen Weise, wie wir die Komposition der Vererbung vorgezogen haben, ist es auch sinnvoll, Monaden der Vererbung vorzuziehen?
(Übrigens war ich mir nicht sicher, ob dies hier oder in Comp Sci sein sollte, aber dies scheint eher ein praktisches Modellierungsproblem zu sein. Aber vielleicht ist es dort besser.)
quelle
Antworten:
Die kurze Antwort lautet: Nein , Monaden sind keine Alternative zu Vererbungshierarchien (auch als Subtyp-Polymorphismus bezeichnet). Sie scheinen den parametrischen Polymorphismus zu beschreiben , den Monaden nutzen, aber nicht die einzigen sind, die dies tun.
Nach meinem Verständnis haben Monaden im Wesentlichen nichts mit Vererbung zu tun. Ich würde sagen, dass die beiden Dinge mehr oder weniger orthogonal sind: Sie sollen verschiedene Probleme angehen, und so:
Obwohl dies tangential zu Ihrer Frage ist, sind Sie vielleicht interessiert zu erfahren, dass Monaden unglaublich mächtige Möglichkeiten zum Komponieren haben. Lesen Sie mehr über Monadentransformatoren. Dies ist jedoch immer noch ein aktives Forschungsgebiet, da wir (und damit meine ich Leute, die 100.000-mal schlauer sind als ich) keine großartigen Möglichkeiten gefunden haben, Monaden zu komponieren, und es scheint, als würden einige Monaden nicht willkürlich komponieren.
Nun, um Ihre Frage zu beantworten (Entschuldigung, ich beabsichtige, dass dies hilfreich ist und Sie sich nicht schlecht fühlen lässt): Ich habe das Gefühl, dass es viele fragwürdige Prämissen gibt, auf die ich versuchen werde, etwas Licht ins Dunkel zu bringen.
Nein, dies ist
Monad
in Haskell: ein parametrisierter Typm a
mit einer Implementierung vonreturn :: a -> m a
und(>>=) :: m a -> (a -> m b) -> m b
, der die folgenden Gesetze erfüllt:Es gibt einige Instanzen von Monad, bei denen es sich nicht um container (
(->) b
) handelt, und einige Container, bei denen es sich nicht um Instanzen von Monad handelt (Set
und die aufgrund der Einschränkung der Typklasse nicht erstellt werden können ). Die "Container" -Intuition ist also schlecht. Sehen Sie dies für weitere Beispiele.Nein überhaupt nicht. Für dieses Beispiel ist keine Monade erforderlich. Es werden lediglich Funktionen mit passenden Ein- und Ausgabetypen benötigt. Hier ist eine andere Möglichkeit, es zu schreiben, die unterstreicht, dass es sich nur um eine Funktionsanwendung handelt:
Ich glaube, dies ist ein Muster, das als "flüssige Schnittstelle" oder "Methodenverkettung" bekannt ist (aber ich bin mir nicht sicher).
Datentypen, die auch Monaden sind, können (und tun dies fast immer!) Operationen haben, die nicht mit Monaden zusammenhängen. Hier ist ein Haskell-Beispiel, das aus drei Funktionen besteht,
[]
die nichts mit Monaden zu tun haben:[]
"Definiert und steuert die Semantik der Operation" und die "enthaltene Klasse" nicht, aber das reicht nicht aus, um eine Monade zu erstellen:Sie haben richtig bemerkt, dass es Probleme mit der Verwendung von Klassenhierarchien zum Modellieren von Dingen gibt. Ihre Beispiele weisen jedoch nicht darauf hin, dass Monaden Folgendes können:
quelle
land(flyAround(takeOff(new Flier<Duck>(duck))))
funktioniert nicht (zumindest in OO), da diese Konstruktion eine Unterbrechung der Kapselung erfordert, um an die Details von Flier zu gelangen. Durch die Verkettung von Operationen in der Klasse bleiben die Details von Flier verborgen, und die Semantik bleibt erhalten. Das ähnelt dem Grund, warum in Haskell eine Monade bindet(a, M b)
und nicht,(M a, M b)
damit die Monade ihren Zustand nicht der "Action" -Funktion aussetzen muss.unit
wird (meistens) Konstruktor für den enthaltenen Typ undbind
wird (meistens) eine implizite Kompilierungszeitoperation (dh eine frühe Bindung), die die "Aktions" -Funktionen mit der Klasse verbindet. Wenn Sie erstklassige Funktionen oder eine Function <A-, Monad <B >> -Klasse haben, kann einebind
Methode eine späte Bindung ausführen, aber ich werde diesen Missbrauch als nächstes übernehmen. ;)Flier<Thing>
die Semantik des Flugs gesteuert wird, können viele Daten und Operationen verfügbar gemacht werden, die die Flugsemantik beibehalten, während die "monad" -spezifische Semantik eigentlich nur die Verkettung und Verkapselung bewirkt. Diese Bedenken betreffen möglicherweise nicht (und mit denen, die ich verwendet habe, auch nicht) die Klasse innerhalb der Monade:Resource<String>
Hat z. B. eine httpStatus-Eigenschaft, String jedoch nicht.In Nicht-OO-Sprachen ja. In traditionelleren OO-Sprachen würde ich nein sagen.
Das Problem ist , dass die meisten Sprachen nicht Typ Spezialisierung haben, so dass Sie nicht machen können
Flier<Squirrel>
undFlier<Bird>
verschiedene Implementierungen haben. Sie müssen so etwas tunstatic Flier Flier::Create(Squirrel)
(und dann für jeden Typ überladen). Dies bedeutet wiederum, dass Sie diesen Typ jedes Mal ändern müssen, wenn Sie ein neues Tier hinzufügen, und wahrscheinlich ziemlich viel Code duplizieren müssen, damit es funktioniert.Oh, und in nicht wenigen Sprachen (C # zum Beispiel)
public class Flier<T> : T {}
ist illegal. Es wird nicht einmal bauen. Die meisten, wenn nicht alle OO-Programmierer, würden damit rechnenFlier<Bird>
, immer noch einer zu seinBird
.quelle
Flier<Bird>
es sich um einen parametrisierten Container handelt, würde es niemand alsBird
(!?)List<String>
Eine Liste und nicht als String ansehen.Flier
ist nicht nur ein Container. Wenn Sie es nur für einen Container halten, warum würden Sie jemals denken, dass es die Verwendung von Vererbung ersetzen könnte?Animal / Bird / Penguin
ist in der Regel ein schlechtes Beispiel, weil es alle Arten von Semantik bringt. Ein praktisches Beispiel ist eine REST-ische Monade, die wir verwenden:Resource<String>.from(uri).get()
Resource
fügt der SemantikString
(oder einem anderen Typ) eine Semantik hinzu , es ist also offensichtlich keineString
.