Was ist eine Monade, die ein OOP-Programmierer verstehen würde (ohne funktionalen Programmierhintergrund)?
Welches Problem löst es und an welchen Orten wird es am häufigsten verwendet?
BEARBEITEN:
Nehmen wir an, Sie haben eine FP-Anwendung mit Monaden in eine OOP-Anwendung konvertiert, um das von mir gesuchte Verständnis zu verdeutlichen. Was würden Sie tun, um die Verantwortlichkeiten der Monaden auf die OOP-App zu übertragen?
Antworten:
UPDATE: Diese Frage war Gegenstand einer immens langen Blogserie, die Sie bei Monads lesen können - danke für die tolle Frage!
Eine Monade ist ein "Verstärker" von Typen , der bestimmten Regeln folgt und für den bestimmte Operationen vorgesehen sind .
Was ist ein "Verstärker der Typen"? Damit meine ich ein System, mit dem Sie einen Typ in einen spezielleren Typ verwandeln können. Zum Beispiel in C # betrachten
Nullable<T>
. Dies ist ein Verstärker von Typen. Sie können beispielsweise einen Typ nehmenint
und diesem Typ eine neue Funktion hinzufügen, nämlich, dass er jetzt null sein kann, wenn dies vorher nicht möglich war.Betrachten Sie als zweites Beispiel
IEnumerable<T>
. Es ist ein Verstärker von Typen. Sie können beispielsweise einen Typ nehmenstring
und diesem Typ eine neue Funktion hinzufügen, nämlich, dass Sie jetzt aus einer beliebigen Anzahl einzelner Zeichenfolgen eine Folge von Zeichenfolgen erstellen können.Was sind die "bestimmten Regeln"? Kurz gesagt, dass es eine sinnvolle Möglichkeit für Funktionen des zugrunde liegenden Typs gibt, am verstärkten Typ so zu arbeiten, dass sie den normalen Regeln der funktionalen Zusammensetzung folgen. Zum Beispiel, wenn Sie eine Funktion für ganze Zahlen haben, sagen wir
dann kann die entsprechende Funktion ein
Nullable<int>
dazu führen, dass alle Operatoren und Aufrufe dort "auf die gleiche Weise" wie zuvor zusammenarbeiten.(Das ist unglaublich vage und ungenau. Sie haben um eine Erklärung gebeten, die nichts über das Wissen über die funktionale Zusammensetzung voraussetzt.)
Was sind die "Operationen"?
Es gibt eine "Einheit" -Operation (verwirrenderweise manchmal als "Rückgabe" -Operation bezeichnet), die einen Wert von einem einfachen Typ nimmt und den entsprechenden monadischen Wert erzeugt. Dies bietet im Wesentlichen eine Möglichkeit, einen Wert eines nicht verstärkten Typs in einen Wert des verstärkten Typs umzuwandeln. Es könnte als Konstruktor in einer OO-Sprache implementiert werden.
Es gibt eine "Bind" -Operation, die einen monadischen Wert und eine Funktion verwendet, die den Wert transformieren kann, und einen neuen monadischen Wert zurückgibt. Binden ist die Schlüsseloperation, die die Semantik der Monade definiert. Damit können wir Operationen für den nicht verstärkten Typ in Operationen für den erweiterten Typ umwandeln, die den zuvor erwähnten Regeln der funktionalen Zusammensetzung entsprechen.
Es gibt oft eine Möglichkeit, den unverstärkten Typ wieder aus dem verstärkten Typ herauszuholen. Genau genommen ist für diese Operation keine Monade erforderlich. (Obwohl es notwendig ist, wenn Sie eine Comonade haben möchten . Wir werden diese in diesem Artikel nicht weiter betrachten.)
Nehmen Sie noch einmal
Nullable<T>
als Beispiel. Sie können mit dem Konstruktor einint
in ein verwandelnNullable<int>
. Der C # -Compiler kümmert sich für Sie um das am meisten nullbare "Heben", aber wenn dies nicht der Fall ist, ist die Hebungstransformation unkompliziert: eine Operation, sagen wir,verwandelt sich in
Und mit der Immobilie wird aus einem
Nullable<int>
Rücken ein Rückenint
gemachtValue
.Es ist die Funktionstransformation, die das Schlüsselbit ist. Beachten Sie, wie die tatsächliche Semantik der nullbaren Operation - dass eine Operation auf einer
null
propagiertnull
- in der Transformation erfasst wird. Wir können das verallgemeinern.Angenommen, Sie haben eine Funktion von
int
bisint
, wie unser OriginalM
. Sie können dies leicht zu einer Funktion machen, die a annimmtint
und a zurückgibt,Nullable<int>
da Sie das Ergebnis einfach über den nullbaren Konstruktor ausführen können. Angenommen, Sie haben diese Methode höherer Ordnung:Sehen Sie, was Sie damit machen können? Auf jede Methode, die ein
int
und ein zurückgibtint
oder einint
und ein zurückgibt,Nullable<int>
kann jetzt die nullbare Semantik angewendet werden .Außerdem: Angenommen, Sie haben zwei Methoden
und du willst sie komponieren:
Das heißt,
Z
ist die Zusammensetzung vonX
undY
. Aber Sie können das nicht tun, weil Sie aX
nehmenint
undY
a zurückgebenNullable<int>
. Da Sie jedoch die "Bind" -Operation haben, können Sie Folgendes ausführen:Die Bindeoperation für eine Monade bewirkt, dass die Zusammensetzung von Funktionen für verstärkte Typen funktioniert. Die "Regeln", über die ich oben mit der Hand geschwenkt habe, sind, dass die Monade die Regeln der normalen Funktionszusammensetzung beibehält; Das Komponieren mit Identitätsfunktionen führt zur ursprünglichen Funktion, das Komponieren ist assoziativ und so weiter.
In C # heißt "Bind" "SelectMany". Schauen Sie sich an, wie es mit der Sequenzmonade funktioniert. Wir müssen zwei Dinge haben: einen Wert in eine Sequenz verwandeln und Operationen an Sequenzen binden. Als Bonus haben wir auch "eine Sequenz wieder in einen Wert verwandeln". Diese Operationen sind:
Die Null-Monaden-Regel lautete: "Kombinieren Sie zwei Funktionen, die Null-Werte erzeugen, und prüfen Sie, ob die innere Null ergibt. Wenn dies der Fall ist, erzeugen Sie Null. Wenn dies nicht der Fall ist, rufen Sie die äußere mit dem Ergebnis auf." Das ist die gewünschte Semantik von nullable.
Die Sequenzmonadenregel lautet: "Kombinieren Sie zwei Funktionen, die Sequenzen erzeugen, wenden Sie die äußere Funktion auf jedes Element an, das von der inneren Funktion erzeugt wird, und verketten Sie dann alle resultierenden Sequenzen miteinander." Die grundlegende Semantik der Monaden wird in den
Bind
/SelectMany
Methoden erfasst ; Dies ist die Methode, die Ihnen sagt, was die Monade wirklich bedeutet .Wir können es noch besser machen. Angenommen, Sie haben eine Folge von Ints und eine Methode, die Ints nimmt und zu Folgen von Strings führt. Wir könnten die Bindungsoperation verallgemeinern, um die Zusammensetzung von Funktionen zu ermöglichen, die verschiedene verstärkte Typen annehmen und zurückgeben, solange die Eingänge des einen mit den Ausgängen des anderen übereinstimmen:
Jetzt können wir also sagen: "Verstärken Sie diese Gruppe einzelner Ganzzahlen in eine Folge von Ganzzahlen. Transformieren Sie diese bestimmte Ganzzahl in eine Reihe von Zeichenfolgen, die zu einer Folge von Zeichenfolgen verstärkt werden. Fügen Sie nun beide Operationen zusammen: Verstärken Sie diese Gruppe von Ganzzahlen in die Verkettung von alle Folgen von Strings. " Monaden können Sie komponieren Ihre Amplifikationen.
Das ist eher so, als würde man fragen "Welche Probleme löst das Singleton-Muster?", Aber ich werde es versuchen.
Monaden werden normalerweise verwendet, um Probleme zu lösen wie:
C # verwendet Monaden in seinem Design. Wie bereits erwähnt, ist das nullbare Muster der "vielleicht Monade" sehr ähnlich. LINQ besteht vollständig aus Monaden; Die
SelectMany
Methode ist das, was die semantische Arbeit der Komposition von Operationen macht. (Erik Meijer weist gern darauf hin, dass jede LINQ-Funktion tatsächlich von implementiert werden könnteSelectMany
; alles andere ist nur eine Annehmlichkeit.)Die meisten OOP-Sprachen verfügen nicht über ein ausreichend umfangreiches Typsystem, um das Monadenmuster selbst direkt darzustellen. Sie benötigen ein Typsystem, das Typen unterstützt, die höhere Typen als generische Typen sind. Also würde ich das nicht versuchen. Vielmehr würde ich generische Typen implementieren, die jede Monade darstellen, und Methoden implementieren, die die drei Operationen darstellen, die Sie benötigen: einen Wert in einen verstärkten Wert umwandeln, (möglicherweise) einen verstärkten Wert in einen Wert umwandeln und eine Funktion für nicht verstärkte Werte in umwandeln eine Funktion für verstärkte Werte.
Ein guter Anfang ist, wie wir LINQ in C # implementiert haben. Studiere die
SelectMany
Methode; Dies ist der Schlüssel zum Verständnis der Funktionsweise der Sequenzmonade in C #. Es ist eine sehr einfache Methode, aber sehr mächtig!Vorgeschlagene weiterführende Literatur:
quelle
Warum brauchen wir Monaden?
Dann haben wir ein erstes großes Problem. Dies ist ein Programm:
f(x) = 2 * x
g(x,y) = x / y
Wie können wir sagen, was zuerst ausgeführt werden soll ? Wie können wir eine geordnete Folge von Funktionen (dh ein Programm ) mit nur Funktionen bilden?
Lösung: Funktionen erstellen . Wenn Sie zuerst
g
und dann wollenf
, schreiben Sie einfachf(g(x,y))
. OK aber ...Weitere Probleme: Einige Funktionen können fehlschlagen (dh
g(2,0)
durch 0 teilen). Wir haben keine "Ausnahmen" in FP . Wie lösen wir das?Lösung: Lassen Sie zu, dass Funktionen zwei Arten von Dingen zurückgeben : Anstatt
g : Real,Real -> Real
(Funktion von zwei Real in ein Real) zu haben, lassen Sie unsg : Real,Real -> Real | Nothing
(Funktion von zwei Real in (Real oder nichts)) zulassen .Aber Funktionen sollten (um einfacher zu sein) nur eines zurückgeben .
Lösung: Lassen Sie uns einen neuen Datentyp erstellen, der zurückgegeben werden soll, einen " Boxtyp ", der möglicherweise einen echten oder einfach nichts enthält. Daher können wir haben
g : Real,Real -> Maybe Real
. OK aber ...Was passiert jetzt mit
f(g(x,y))
?f
ist nicht bereit zu konsumieren aMaybe Real
. Und wir möchten nicht jede Funktion ändern, mit der wir uns verbinden könneng
, um a zu konsumierenMaybe Real
.Lösung: Lassen Sie uns eine spezielle Funktion zum "Verbinden" / "Verfassen" / "Verknüpfen" von Funktionen haben . Auf diese Weise können wir hinter den Kulissen die Ausgabe einer Funktion anpassen, um die folgende zu speisen.
In unserem Fall:
g >>= f
(verbinden / komponiereng
mitf
). Wir möchten>>=
dieg
AusgabeNothing
abrufen , sie überprüfen und, falls dies nicht der Fall ist , nicht anrufenf
und zurückgebenNothing
. oder im Gegenteil, extrahieren Sie die BoxReal
und fütternf
Sie damit. (Dieser Algorithmus ist nur die Implementierung>>=
für denMaybe
Typ).Es treten viele andere Probleme auf, die mit demselben Muster gelöst werden können: 1. Verwenden Sie eine "Box", um verschiedene Bedeutungen / Werte zu codieren / zu speichern, und lassen Sie
g
solche Funktionen diese "Box-Werte" zurückgeben. 2. Lassen Sie Komponisten / Linkerg >>= f
helfeng
, die Ausgabe mitf
der Eingabe zu verbinden, damit wir uns überhaupt nicht ändern müssenf
.Bemerkenswerte Probleme, die mit dieser Technik gelöst werden können, sind:
einen globalen Zustand haben, den jede Funktion in der Folge von Funktionen ("das Programm") gemeinsam nutzen kann: Lösung
StateMonad
.Wir mögen keine "unreinen Funktionen": Funktionen, die für dieselbe Eingabe unterschiedliche Ausgaben liefern . Markieren wir diese Funktionen daher so, dass sie einen Wert mit Tags / Boxen zurückgeben: monad.
IO
Totales Glück !!!!
quelle
State
undIO
mit keinem von ihnen sowie der genauen Bedeutung von "Monade"Ich würde sagen, die OO-Analogie zu Monaden ist das " Befehlsmuster ".
In dem Befehl Muster , das Sie eine gewöhnliche Aussage oder Ausdruck in einem wickeln Befehl Objekt. Das Befehlsobjekt macht eine Ausführungsmethode verfügbar, die die umschlossene Anweisung ausführt. So werden Anweisungen zu erstklassigen Objekten, die nach Belieben weitergegeben und ausgeführt werden können. Befehle können so zusammengesetzt werden, dass Sie ein Programmobjekt erstellen können, indem Sie Befehlsobjekte verketten und verschachteln.
Die Befehle werden von einem separaten Objekt, dem Aufrufer, ausgeführt . Der Vorteil der Verwendung des Befehlsmusters (anstatt nur eine Reihe gewöhnlicher Anweisungen auszuführen) besteht darin, dass verschiedene Aufrufer unterschiedliche Logik auf die Ausführung der Befehle anwenden können.
Das Befehlsmuster kann verwendet werden, um Sprachfunktionen hinzuzufügen (oder zu entfernen), die von der Hostsprache nicht unterstützt werden. In einer hypothetischen OO-Sprache ohne Ausnahmen können Sie beispielsweise eine Ausnahmesemantik hinzufügen, indem Sie den Befehlen die Methoden "try" und "throw" zur Verfügung stellen. Wenn ein Befehl throw aufruft, geht der Aufrufer die Liste (oder den Baum) der Befehle bis zum letzten "try" -Aufruf zurück. Umgekehrt können Sie die Ausnahmesemantik aus einer Sprache entfernen (wenn Sie glauben, dass Ausnahmen schlecht sind ), indem Sie alle von den einzelnen Befehlen ausgelösten Ausnahmen abfangen und in Fehlercodes umwandeln, die dann an den nächsten Befehl übergeben werden.
Noch ausgefallenere Ausführungssemantiken wie Transaktionen, nicht deterministische Ausführung oder Fortsetzungen können so in einer Sprache implementiert werden, die sie nicht nativ unterstützt. Es ist ein ziemlich starkes Muster, wenn Sie darüber nachdenken.
In Wirklichkeit werden die Befehlsmuster nicht als solche allgemeine Sprachfunktion verwendet. Der Aufwand, jede Anweisung in eine separate Klasse umzuwandeln, würde zu einer unerträglichen Menge an Code führen. Aber im Prinzip kann es verwendet werden, um die gleichen Probleme zu lösen, mit denen Monaden in fp gelöst werden.
quelle
In Bezug auf die OO-Programmierung ist eine Monade eine Schnittstelle (oder eher ein Mixin), die durch einen Typ mit zwei Methoden parametrisiert wird
return
undbind
Folgendes beschreibt:Das Problem, das es löst, ist die gleiche Art von Problem, die Sie von jeder Schnittstelle erwarten würden, nämlich "Ich habe eine Reihe verschiedener Klassen, die verschiedene Dinge tun, aber diese verschiedenen Dinge auf eine Weise zu tun scheinen, die eine zugrunde liegende Ähnlichkeit hat. Wie Kann ich diese Ähnlichkeit zwischen ihnen beschreiben, auch wenn die Klassen selbst keine Subtypen von etwas sind, das näher liegt als die 'The Object'-Klasse selbst? "
Insbesondere ist die
Monad
"Schnittstelle" ähnlichIEnumerator
oderIIterator
dahingehend, dass sie einen Typ annimmt, der selbst einen Typ annimmt. Der Hauptpunkt dabeiMonad
ist jedoch, Operationen basierend auf dem Innentyp zu verbinden, sogar bis zu einem neuen "internen Typ", während die Informationsstruktur der Hauptklasse beibehalten oder sogar verbessert wird.quelle
return
wäre eigentlich keine Methode für die Monade, da keine Monadeninstanz als Argument verwendet wird. (dh: es gibt kein dies / selbst)Sie haben kürzlich eine Präsentation " Monadologie - professionelle Hilfe bei Typangst " von Christopher League (12. Juli 2010), die zu Themen wie Fortsetzung und Monade sehr interessant ist.
Das Video zu dieser Präsentation (Diashow) ist tatsächlich bei vimeo verfügbar .
Der Monadenteil beginnt in diesem einstündigen Video in etwa 37 Minuten und beginnt mit Folie 42 seiner 58 Folienpräsentation.
Es wird als "das führende Entwurfsmuster für die funktionale Programmierung" dargestellt, aber die in den Beispielen verwendete Sprache ist Scala, die sowohl OOP als auch funktional ist.
Weitere Informationen zu Monad in Scala finden Sie im Blog-Beitrag " Monaden - Ein anderer Weg, um Berechnungen in Scala zu abstrahieren " von Debasish Ghosh (27. März 2008).
So zum Beispiel (in Scala):
Option
ist eine MonadeList
ist MonadeMonaden sind eine große Sache in Scala, da die Syntax so gestaltet ist, dass sie die Vorteile von Monadenstrukturen nutzt:
for
Verständnis in Scala :wird vom Compiler übersetzt in:
Die Schlüsselabstraktion ist die
flatMap
, die die Berechnung durch Verkettung bindet.Jeder Aufruf von
flatMap
gibt denselben Datenstrukturtyp zurück (jedoch von unterschiedlichem Wert), der als Eingabe für den nächsten Befehl in der Kette dient.Im obigen Snippet nimmt flatMap einen Abschluss als Eingabe
(SomeType) => List[AnotherType]
und gibt a zurückList[AnotherType]
. Der wichtige Punkt ist, dass alle flatMaps denselben Schließungstyp als Eingabe verwenden und denselben Typ wie Ausgabe zurückgeben.Dies ist es, was den Berechnungsthread "bindet" - jedes Element der Sequenz im Verständnis muss dieselbe Typbeschränkung berücksichtigen.
Wenn Sie zwei Operationen ausführen (die möglicherweise fehlschlagen) und das Ergebnis an die dritte übergeben, wie z.
Aber ohne Monad auszunutzen, erhalten Sie verschlungenen OOP-Code wie:
Während Sie mit Monad mit den tatsächlichen Typen (
Venue
,User
) arbeiten können, wie alle Operationen funktionieren, und das Optionsüberprüfungsmaterial aufgrund der Flatmaps der for-Syntax verborgen halten können:Der Yield-Teil wird nur ausgeführt, wenn alle drei Funktionen haben
Some[X]
; JederNone
würde direkt an zurückgegeben werdenconfirm
.Damit:
Monad ist übrigens nicht nur ein Berechnungsmodell, das in FP verwendet wird:
quelle
Um schnelle Leser zu respektieren, beginne ich zuerst mit einer genauen Definition, fahre mit einer schnelleren "einfacheren englischen" Erklärung fort und gehe dann zu Beispielen über.
Hier ist eine präzise und präzise Definition, die leicht umformuliert wurde:
In einfachen Worten, eine Monade ist eine Regel, die von einem beliebigen Typ
X
an einen anderen Typ übergeben werden mussT(X)
, und eine Regel, die von zwei Funktionenf:X->T(Y)
undg:Y->T(Z)
(die Sie komponieren möchten, aber nicht können) an eine neue Funktion übergeben werden kannh:X->T(Z)
. Was jedoch nicht die Zusammensetzung im strengen mathematischen Sinne ist. Wir "biegen" im Grunde die Zusammensetzung der Funktion oder definieren neu, wie Funktionen zusammengesetzt sind.Außerdem benötigen wir die Kompositionsregel der Monade, um die "offensichtlichen" mathematischen Axiome zu erfüllen:
f
mitg
und dann mith
(von außen) sollte dasselbe sein wie das Komponiereng
mith
und dann mitf
(von innen).f
mit der Identitätsfunktion auf beiden Seiten sollte ergebenf
.Mit einfachen Worten, wir können nicht einfach verrückt werden, wenn wir unsere Funktionszusammensetzung neu definieren, wie wir möchten:
f(g(h(k(x)))
und um die Reihenfolge der Funktionspaare nicht zusammensetzen zu müssen. Da die Monadenregel nur vorschreibt, wie ein Funktionspaar ohne dieses Axiom zusammengesetzt werden soll, müssten wir wissen, welches Paar zuerst zusammengesetzt wird und so weiter. (Beachten Sie, dass sich dies von der Kommutativitätseigenschaft unterscheidet,f
mitg
derg
zusammengesetzt wurdef
, die mit der zusammengesetzten Eigenschaft identisch ist , was nicht erforderlich ist.)Also noch einmal kurz: Eine Monade ist die Regel der Typerweiterung und der Kompositionsfunktionen, die die beiden Axiome - Assoziativität und unitales Eigentum - erfüllen.
In der Praxis möchten Sie, dass die Monade von der Sprache, dem Compiler oder dem Framework für Sie implementiert wird, die sich um das Erstellen von Funktionen für Sie kümmern. Sie können sich also darauf konzentrieren, die Logik Ihrer Funktion zu schreiben, anstatt sich Gedanken darüber zu machen, wie ihre Ausführung implementiert wird.
Das ist es im Wesentlichen, kurz gesagt.
Als professioneller Mathematiker vermeide ich es lieber,
h
die "Komposition" vonf
und zu nenneng
. Weil es mathematisch nicht so ist. Wenn man es die "Komposition" nennt,h
wird fälschlicherweise davon ausgegangen, dass es sich um die wahre mathematische Komposition handelt, die es nicht ist. Es wird nicht einmal eindeutig vonf
und bestimmtg
. Stattdessen ist es das Ergebnis der neuen "Regel" unserer Monade, die Funktionen zusammenzusetzen. Was sich von der tatsächlichen mathematischen Zusammensetzung völlig unterscheiden kann, selbst wenn letztere existiert!Um es weniger trocken zu machen, möchte ich versuchen, es anhand eines Beispiels zu veranschaulichen, das ich mit kleinen Abschnitten kommentiere, damit Sie direkt zum Punkt springen können.
Ausnahme werfen als Monadenbeispiele
Angenommen, wir möchten zwei Funktionen zusammensetzen:
Ist
f(0)
aber nicht definiert, wird eine Ausnahmee
ausgelöst. Wie können Sie dann den Kompositionswert definiereng(f(0))
? Wirf natürlich wieder eine Ausnahme! Vielleicht das gleichee
. Möglicherweise eine neue aktualisierte Ausnahmee1
.Was genau passiert hier? Erstens benötigen wir neue Ausnahmewerte (unterschiedlich oder gleich). Sie können sie
nothing
odernull
was auch immer nennen, aber die Essenz bleibt dieselbe - sie sollten neue Werte sein, z. B. sollte esnumber
in unserem Beispiel hier kein sein . Ich ziehe es vor, sie nicht anzurufennull
, um Verwechslungen mitnull
der Implementierung in einer bestimmten Sprache zu vermeiden . Ebenso bevorzuge ich es zu vermeiden,nothing
weil es oft damit verbunden istnull
, was im Prinzip zunull
tun ist, aber dieses Prinzip wird oft aus welchen praktischen Gründen auch immer verbogen.Was genau ist eine Ausnahme?
Dies ist für jeden erfahrenen Programmierer eine triviale Angelegenheit, aber ich möchte ein paar Worte fallen lassen, um jeden Wurm der Verwirrung auszulöschen:
Ausnahme ist ein Objekt, das Informationen darüber enthält, wie das ungültige Ergebnis der Ausführung aufgetreten ist.
Dies kann vom Wegwerfen von Details und Zurückgeben eines einzelnen globalen Werts (wie
NaN
odernull
) oder vom Generieren einer langen Protokollliste oder was genau passiert ist, vom Senden an eine Datenbank und Replizieren über die gesamte verteilte Datenspeicherschicht reichen;)Der wichtige Unterschied zwischen diesen beiden extremen Ausnahmebeispielen besteht darin, dass im ersten Fall keine Nebenwirkungen auftreten . In der zweiten gibt es. Das bringt uns zur (Tausend-Dollar-) Frage:
Sind Ausnahmen in reinen Funktionen erlaubt?
Kürzere Antwort : Ja, aber nur, wenn sie nicht zu Nebenwirkungen führen.
Längere Antwort. Um rein zu sein, muss die Ausgabe Ihrer Funktion eindeutig durch ihre Eingabe bestimmt werden. Deshalb ändern wir unsere Funktion,
f
indem wir0
an den neuen abstrakten Wert senden , dene
wir Ausnahme nennen. Wir stellen sicher, dass der Werte
keine externen Informationen enthält, die nicht eindeutig durch unsere Eingabe bestimmt werdenx
. Hier ist ein Beispiel für eine Ausnahme ohne Nebenwirkung:Und hier ist einer mit Nebeneffekt:
Tatsächlich hat es nur dann Nebenwirkungen, wenn sich diese Meldung möglicherweise in Zukunft ändern kann. Wenn sich jedoch garantiert nie ändert, wird dieser Wert eindeutig vorhersehbar, sodass keine Nebenwirkungen auftreten.
Um es noch dümmer zu machen. Eine Funktion, die
42
jemals zurückkehrt , ist eindeutig rein. Aber wenn jemand Verrückter beschließt,42
eine Variable zu erstellen, deren Wert sich möglicherweise ändert, ist dieselbe Funktion unter den neuen Bedingungen nicht mehr rein.Beachten Sie, dass ich der Einfachheit halber die Objektliteralnotation verwende, um das Wesentliche zu demonstrieren. Leider sind die Dinge in Sprachen wie JavaScript durcheinander, wo
error
es keinen Typ gibt, der sich in Bezug auf die Funktionszusammensetzung so verhält, wie wir es hier wollen, während tatsächliche Typen sich so verhaltennull
oderNaN
nicht so verhalten, sondern eher künstlich und nicht immer intuitiv sind Typkonvertierungen.Typerweiterung
Da wir die Nachricht innerhalb unserer Ausnahme variieren möchten, deklarieren wir wirklich einen neuen Typ
E
für das gesamte Ausnahmeobjekt und dann ist es dasmaybe number
, was das tut, abgesehen von seinem verwirrenden Namen, der entweder vom Typnumber
oder vom neuen Ausnahmetyp sein sollE
Es ist also wirklich die Vereinigungnumber | E
vonnumber
undE
. Insbesondere hängt es davon ab, wie wir konstruieren wollenE
, was im Namen weder vorgeschlagen noch reflektiert wirdmaybe number
.Was ist funktionale Zusammensetzung?
Es ist die mathematische Operation Mitnahmen Funktionen
f: X -> Y
undg: Y -> Z
und deren Zusammensetzung als Funktion der Konstruktionh: X -> Z
erfüllenh(x) = g(f(x))
. Das Problem mit dieser Definition tritt auf, wenn das Ergebnisf(x)
als Argument von nicht zulässig istg
.In der Mathematik können diese Funktionen nicht ohne zusätzliche Arbeit komponiert werden. Die streng mathematische Lösung für unser obiges Beispiel von
f
undg
besteht darin,0
aus dem Definitionssatz von zu entfernenf
. Mit dieser neuen Definition (neue restriktivere Art vonx
)f
wird mit zusammensetzbarg
.Bei der Programmierung ist es jedoch nicht sehr praktisch, den Satz solcher Definitionen einzuschränken
f
. Stattdessen können Ausnahmen verwendet werden.Oder wie ein anderer Ansatz sind künstliche Werte geschaffen wie
NaN
,undefined
,null
,Infinity
etc. So können Sie bewerten1/0
zuInfinity
und1/-0
zu-Infinity
. Und dann zwingen Sie den neuen Wert zurück in Ihren Ausdruck, anstatt eine Ausnahme auszulösen. Zu Ergebnissen führen, die Sie möglicherweise vorhersehbar finden oder nicht:Und wir sind zurück zu regulären Zahlen, die bereit sind, weiterzumachen;)
Mit JavaScript können wir numerische Ausdrücke um jeden Preis ausführen, ohne Fehler wie im obigen Beispiel zu verursachen. Das heißt, es ermöglicht auch das Zusammenstellen von Funktionen. Genau darum geht es in der Monade - es ist eine Regel, Funktionen zu komponieren, die den zu Beginn dieser Antwort definierten Axiomen entsprechen.
Aber ist die Regel der Kompositionsfunktion, die sich aus der Implementierung von JavaScript für den Umgang mit numerischen Fehlern ergibt, eine Monade?
Um diese Frage zu beantworten, müssen Sie nur die Axiome überprüfen (als Übung als Teil der Frage hier belassen;).
Kann eine Wurfausnahme verwendet werden, um eine Monade zu konstruieren?
In der Tat wäre eine nützlichere Monade stattdessen die Regel, die vorschreibt, dass, wenn
f
für einige eine Ausnahme ausgelöst wirdx
, dies auch für die Zusammensetzung bei einigen giltg
. Machen Sie die AusnahmeE
mit nur einem möglichen Wert ( Terminalobjekt in der Kategorietheorie) global eindeutig . Jetzt sind die beiden Axiome sofort überprüfbar und wir erhalten eine sehr nützliche Monade. Und das Ergebnis ist die sogenannte Monade .quelle
Eine Monade ist ein Datentyp, der einen Wert kapselt und auf den im Wesentlichen zwei Operationen angewendet werden können:
return x
Erstellt einen Wert des Monadentyps, der kapseltx
m >>= f
(Lesen Sie es als "Bindungsoperator") Wendet die Funktionf
auf den Wert in der Monade anm
Das ist eine Monade. Es gibt noch ein paar technische Details , aber im Grunde definieren diese beiden Operationen eine Monade. Die eigentliche Frage ist : „Was ist eine Monade tut ?“, Und das hängt von der Monade - Listen sind Monaden, Maybes Monaden sind, IO - Operationen sind Monaden. Alles was es bedeutet, wenn wir sagen, dass diese Dinge Monaden sind, ist, dass sie die Monadenschnittstelle von
return
und haben>>=
.quelle
bind
Funktion ab, die für jeden Monadentyp definiert werden muss, nicht wahr? Das wäre ein guter Grund, die Bindung nicht mit der Komposition zu verwechseln, da es eine einzige Definition für die Komposition gibt, während es nicht nur eine einzige Definition für eine Bindungsfunktion geben kann, sondern eine pro monadischem Typ, wenn ich das richtig verstehe.Aus Wikipedia :
Ich glaube, das erklärt es sehr gut.
quelle
Ich werde versuchen, die kürzeste Definition zu erstellen, die ich mit OOP-Begriffen verwalten kann:
Eine generische Klasse
CMonadic<T>
ist eine Monade, wenn sie mindestens die folgenden Methoden definiert:und wenn die folgenden Gesetze für alle Typen T und ihre möglichen Werte t gelten
linke Identität:
richtige Identität
Assoziativität:
Beispiele :
Eine Listenmonade kann Folgendes haben:
Und flatMap auf der Liste [1,2,3] könnte folgendermaßen funktionieren:
Iterables und Observables können ebenso monadisch gemacht werden wie Versprechen und Aufgaben.
Kommentar :
Monaden sind nicht so kompliziert. Die
flatMap
Funktion ist der am häufigsten anzutreffenden sehr ähnlichmap
. Es empfängt ein Funktionsargument (auch als Delegat bezeichnet), das es (sofort oder später, null oder mehrmals) mit einem Wert aus der generischen Klasse aufrufen kann. Es wird erwartet, dass diese übergebene Funktion auch ihren Rückgabewert in dieselbe Art von generischer Klasse einschließt. Um dies zu unterstützencreate
, wird ein Konstruktor bereitgestellt, der aus einem Wert eine Instanz dieser generischen Klasse erstellen kann. Das Rückgabeergebnis von flatMap ist ebenfalls eine generische Klasse desselben Typs, die häufig dieselben Werte packt, die in den Rückgabeergebnissen einer oder mehrerer Anwendungen von flatMap enthalten waren, auf die zuvor enthaltenen Werte. Auf diese Weise können Sie flatMap so oft verketten, wie Sie möchten:Es kommt einfach so vor, dass diese Art von generischer Klasse als Basismodell für eine Vielzahl von Dingen nützlich ist. Dies (zusammen mit den Jargonismen der Kategorietheorie) ist der Grund, warum Monaden so schwer zu verstehen oder zu erklären scheinen. Sie sind eine sehr abstrakte Sache und werden offensichtlich erst dann nützlich, wenn sie spezialisiert sind.
Beispielsweise können Sie Ausnahmen mit monadischen Containern modellieren. Jeder Container enthält entweder das Ergebnis der Operation oder den aufgetretenen Fehler. Die nächste Funktion (Delegat) in der Kette der flatMap-Rückrufe wird nur aufgerufen, wenn die vorherige einen Wert in den Container gepackt hat. Andernfalls wird der Fehler, wenn ein Fehler gepackt wurde, weiter durch die verketteten Container übertragen, bis ein Container gefunden wird, an den eine Fehlerbehandlungsfunktion über eine aufgerufene Methode angehängt ist (eine
.orElse()
solche Methode wäre eine zulässige Erweiterung).Hinweise : Mit funktionalen Sprachen können Sie Funktionen schreiben, die für jede Art von monadischer generischer Klasse ausgeführt werden können. Damit dies funktioniert, müsste man eine generische Schnittstelle für Monaden schreiben. Ich weiß nicht, ob es möglich ist, eine solche Schnittstelle in C # zu schreiben, aber soweit ich weiß, ist es nicht:
quelle
Ob eine Monade in OO eine "natürliche" Interpretation hat, hängt von der Monade ab. In einer Sprache wie Java können Sie die möglicherweise Monade in die Sprache der Überprüfung auf Nullzeiger übersetzen, sodass Berechnungen, die fehlschlagen (dh in Haskell nichts erzeugen), Nullzeiger als Ergebnisse ausgeben. Sie können die Statusmonade in die Sprache übersetzen, die durch Erstellen einer veränderlichen Variablen und Methoden zum Ändern ihres Status generiert wird.
Eine Monade ist ein Monoid in der Kategorie der Endofunktoren.
Die Informationen, die dieser Satz zusammenstellt, sind sehr tief. Und Sie arbeiten in einer Monade mit jeder imperativen Sprache. Eine Monade ist eine "sequenzierte" domänenspezifische Sprache. Es erfüllt bestimmte interessante Eigenschaften, die zusammen eine Monade zu einem mathematischen Modell der "imperativen Programmierung" machen. Haskell macht es einfach, kleine (oder große) imperative Sprachen zu definieren, die auf verschiedene Arten kombiniert werden können.
Als OO-Programmierer verwenden Sie die Klassenhierarchie Ihrer Sprache, um die Arten von Funktionen oder Prozeduren zu organisieren, die in einem Kontext aufgerufen werden können, den Sie als Objekt bezeichnen. Eine Monade ist auch eine Abstraktion dieser Idee, da verschiedene Monaden auf beliebige Weise kombiniert werden können, wodurch alle Methoden der Submonade effektiv in den Geltungsbereich "importiert" werden.
Architektonisch verwendet man dann Typensignaturen, um explizit auszudrücken, welche Kontexte zur Berechnung eines Wertes verwendet werden können.
Zu diesem Zweck kann man Monadentransformatoren verwenden, und es gibt eine hochwertige Sammlung aller "Standard" -Monaden:
mit entsprechenden Monadentransformatoren und Typklassen. Typklassen ermöglichen einen komplementären Ansatz zum Kombinieren von Monaden durch Vereinheitlichen ihrer Schnittstellen, sodass konkrete Monaden eine Standardschnittstelle für die "Art" der Monaden implementieren können. Beispielsweise enthält das Modul Control.Monad.State eine Klasse MonadState sm, und (State s) ist eine Instanz des Formulars
Die lange Geschichte ist, dass eine Monade ein Funktor ist, der einem Wert "Kontext" hinzufügt, der einen Weg hat, einen Wert in die Monade einzufügen, und der zumindest eine Möglichkeit hat, Werte in Bezug auf den damit verbundenen Kontext zu bewerten in eingeschränkter Weise.
Damit:
ist eine Funktion, die einen Wert vom Typ a in eine Monaden- "Aktion" vom Typ m a einfügt.
ist eine Funktion, die eine Monadenaktion ausführt, ihr Ergebnis auswertet und eine Funktion auf das Ergebnis anwendet. Das Schöne an (>> =) ist, dass das Ergebnis in derselben Monade ist. Mit anderen Worten, in m >> = f zieht (>> =) das Ergebnis aus m heraus und bindet es an f, so dass das Ergebnis in der Monade liegt. (Alternativ können wir sagen, dass (>> =) f in m zieht und es auf das Ergebnis anwendet.) Wenn wir also f :: a -> mb und g :: b -> mc haben, können wir "Sequenz" -Aktionen:
Oder mit "Notation machen"
Der Typ für (>>) leuchtet möglicherweise. Es ist
Es entspricht dem Operator (;) in prozeduralen Sprachen wie C. Es erlaubt Notationen wie:
In der mathematischen und philosophischen Logik haben wir Rahmen und Modelle, die "natürlich" mit Monadismus modelliert sind. Eine Interpretation ist eine Funktion, die den Bereich des Modells untersucht und den Wahrheitswert (oder die Verallgemeinerungen) eines Satzes (oder einer Formel unter Verallgemeinerungen) berechnet. In einer modalen Logik für die Notwendigkeit könnten wir sagen, dass ein Satz notwendig ist, wenn er in "jeder möglichen Welt" wahr ist - wenn er in Bezug auf jeden zulässigen Bereich wahr ist. Dies bedeutet, dass ein Modell in einer Sprache für einen Satz als ein Modell verdichtet werden kann, dessen Domäne aus der Sammlung unterschiedlicher Modelle besteht (eines, das jeder möglichen Welt entspricht). Jede Monade hat eine Methode namens "Join", die Ebenen abflacht, was bedeutet, dass jede Monadenaktion, deren Ergebnis eine Monadenaktion ist, in die Monade eingebettet werden kann.
Noch wichtiger ist, dass die Monade unter der Operation "Layer Stacking" geschlossen wird. So funktionieren Monadentransformatoren: Sie kombinieren Monaden, indem sie "join-like" -Methoden für Typen wie bereitstellen
so dass wir eine Aktion in (MaybeT m) in eine Aktion in m umwandeln können, wodurch Ebenen effektiv reduziert werden. In diesem Fall ist runMaybeT :: MaybeT ma -> m (Vielleicht a) unsere Join-ähnliche Methode. (MaybeT m) ist eine Monade, und MaybeT :: m (Vielleicht a) -> MaybeT ma ist effektiv ein Konstruktor für eine neue Art von Monadenaktion in m.
Eine freie Monade für einen Funktor ist die Monade, die durch Stapeln von f erzeugt wird, mit der Implikation, dass jede Sequenz von Konstruktoren für f ein Element der freien Monade ist (oder genauer gesagt etwas mit der gleichen Form wie der Baum von Sequenzen von Konstruktoren für f). Freie Monaden sind eine nützliche Technik zum Aufbau flexibler Monaden mit einer minimalen Menge an Kesselplatte. In einem Haskell-Programm kann ich freie Monaden verwenden, um einfache Monaden für die "High-Level-Systemprogrammierung" zu definieren, um die Typensicherheit aufrechtzuerhalten (ich verwende nur Typen und ihre Deklarationen. Die Implementierung ist mit der Verwendung von Kombinatoren unkompliziert):
Monadismus ist die zugrunde liegende Architektur für das, was man als "Interpreter" - oder "Befehls" -Muster bezeichnen könnte, das in seiner klarsten Form abstrahiert ist, da jede monadische Berechnung zumindest trivial "ausgeführt" werden muss. (Das Laufzeitsystem führt die E / A-Monade für uns aus und ist der Einstiegspunkt für jedes Haskell-Programm. E / A "steuert" den Rest der Berechnungen, indem E / A-Aktionen der Reihe nach ausgeführt werden.)
Der Typ für Join ist auch der Ort, an dem wir die Aussage erhalten, dass eine Monade ein Monoid in der Kategorie der Endofunktoren ist. Join ist aufgrund seiner Art für theoretische Zwecke in der Regel wichtiger. Aber den Typ zu verstehen bedeutet, Monaden zu verstehen. Join- und Monadentransformator-Join-ähnliche Typen sind effektiv Zusammensetzungen von Endofunktoren im Sinne der Funktionszusammensetzung. Um es in eine Haskell-ähnliche Pseudosprache zu bringen,
Foo :: m (ma) <-> (m. M) a
quelle
Eine Monade ist eine Reihe von Funktionen
(Pst: Ein Array von Funktionen ist nur eine Berechnung).
Anstelle eines echten Arrays (eine Funktion in einem Zellenarray) haben Sie diese Funktionen tatsächlich durch eine andere Funktion verkettet >> =. Mit >> = können die Ergebnisse von Funktion i an die Funktion i + 1 angepasst, Berechnungen zwischen ihnen durchgeführt oder sogar die Funktion i + 1 nicht aufgerufen werden.
Die hier verwendeten Typen sind "Typen mit Kontext". Dies ist ein Wert mit einem "Tag". Die verketteten Funktionen müssen einen "nackten Wert" annehmen und ein markiertes Ergebnis zurückgeben. Eine der Aufgaben von >> = ist es, einen nackten Wert aus seinem Kontext zu extrahieren. Es gibt auch die Funktion "return", die einen nackten Wert nimmt und ihn mit einem Tag verbindet.
Ein Beispiel mit Vielleicht . Verwenden wir es, um eine einfache Ganzzahl zu speichern, auf der Berechnungen durchgeführt werden.
Um zu zeigen, dass Monaden ein Array von Funktionen mit Hilfsoperationen sind, betrachten Sie das Äquivalent zum obigen Beispiel, indem Sie nur ein reales Array von Funktionen verwenden
Und es würde so verwendet werden:
quelle
>>=
ist ein Betreiber\x -> x >>= k >>= l >>= m
es sich um eine Reihe von Funktionen handelt, handelth . g . f
es sich auch nicht um Monaden.In OO-Begriffen ist eine Monade ein fließender Behälter.
Die Mindestanforderung ist eine Definition
class <A> Something
, die einen KonstruktorSomething(A a)
und mindestens eine Methode unterstütztSomething<B> flatMap(Function<A, Something<B>>)
Es zählt wohl auch, ob Ihre Monadenklasse Methoden mit Signatur hat
Something<B> work()
die die Regeln der Klasse beibehalten - der Compiler backt zur Kompilierungszeit in flatMap.Warum ist eine Monade nützlich? Weil es sich um einen Container handelt, der verkettbare Operationen ermöglicht, bei denen die Semantik erhalten bleibt. Zum Beispiel
Optional<?>
bewahrt die Semantik von isPresent fürOptional<String>
,Optional<Integer>
,Optional<MyClass>
etc.Als grobes Beispiel:
Beachten Sie, dass wir mit einer Zeichenfolge beginnen und mit einer Ganzzahl enden. Ziemlich cool.
In OO ist möglicherweise ein wenig Handbewegung erforderlich, aber jede Methode für Something, die eine andere Unterklasse von Something zurückgibt, erfüllt das Kriterium einer Containerfunktion, die einen Container des ursprünglichen Typs zurückgibt.
Auf diese Weise behalten Sie die Semantik bei - dh die Bedeutung und die Operationen des Containers ändern sich nicht, sie umschließen und verbessern lediglich das Objekt im Container.
quelle
Monaden im typischen Gebrauch sind das funktionale Äquivalent der Ausnahmebehandlungsmechanismen der prozeduralen Programmierung.
In modernen prozeduralen Sprachen setzen Sie einen Ausnahmebehandler um eine Folge von Anweisungen, von denen jede eine Ausnahme auslösen kann. Wenn eine der Anweisungen eine Ausnahme auslöst, wird die normale Ausführung der Anweisungsfolge angehalten und an einen Ausnahmebehandler übertragen.
Funktionale Programmiersprachen vermeiden jedoch philosophisch Ausnahmebehandlungsfunktionen aufgrund ihrer "goto" -ähnlichen Natur. Die funktionale Programmierperspektive besteht darin, dass Funktionen keine "Nebenwirkungen" wie Ausnahmen haben sollten, die den Programmfluss stören.
In der Realität können Nebenwirkungen in der realen Welt nicht ausgeschlossen werden, hauptsächlich aufgrund von E / A. Monaden in der funktionalen Programmierung werden verwendet, um dies zu handhaben, indem sie eine Reihe verketteter Funktionsaufrufe annehmen (von denen jeder ein unerwartetes Ergebnis erzeugen kann) und jedes unerwartete Ergebnis in gekapselte Daten umwandeln, die die verbleibenden Funktionsaufrufe noch sicher durchlaufen können.
Der Kontrollfluss bleibt erhalten, aber das unerwartete Ereignis wird sicher gekapselt und behandelt.
quelle
Eine einfache Erklärung der Monaden mit einer Marvel-Fallstudie finden Sie hier .
Monaden sind Abstraktionen, mit denen abhängige Funktionen sequenziert werden, die effektiv sind. Effektiv bedeutet hier, dass sie einen Typ in Form F [A] zurückgeben, zum Beispiel Option [A], wobei Option F ist, genannt Typkonstruktor. Lassen Sie uns dies in 2 einfachen Schritten sehen
Wenn die Funktion jedoch einen Effekttyp wie Option [A] zurückgibt, dh A => F [B], funktioniert die Komposition nicht, um zu B zu gelangen. Wir benötigen A => B, aber wir haben A => F [B].
Wir brauchen einen speziellen Operator, "bind", der weiß, wie man diese Funktionen zusammenführt, die F [A] zurückgeben.
Die "Binden" -Funktion ist für das spezifische F definiert .
Es gibt auch "return" vom Typ A => F [A] für jedes A , das auch für dieses spezifische F definiert ist . Eine Monade sein, F. müssen für diese beiden Funktionen definiert sein.
Somit können wir eine effekt Funktion konstruieren A => F [B] von jeder reinen Funktion A => B ,
Ein gegebenes F kann aber auch seine eigenen undurchsichtigen "eingebauten" Spezialfunktionen von solchen Typen definieren, dass ein Benutzer sich selbst nicht (in einer reinen Sprache) definieren kann, wie z
quelle
Ich teile mein Verständnis von Monaden, die theoretisch möglicherweise nicht perfekt sind. In Monaden geht es um die Verbreitung von Kontexten . Monade ist, dass Sie einen Kontext für einige Daten (oder Datentypen) definieren und dann definieren, wie dieser Kontext mit den Daten in der gesamten Verarbeitungspipeline übertragen wird. Beim Definieren der Kontextausbreitung geht es hauptsächlich darum, zu definieren, wie mehrere Kontexte (desselben Typs) zusammengeführt werden sollen. Die Verwendung von Monaden bedeutet auch, sicherzustellen, dass diese Kontexte nicht versehentlich aus den Daten entfernt werden. Andererseits können andere kontextlose Daten in einen neuen oder vorhandenen Kontext gebracht werden. Dann kann dieses einfache Konzept verwendet werden, um die Richtigkeit der Kompilierungszeit eines Programms sicherzustellen.
quelle
Wenn Sie jemals Powershell verwendet haben, sollten die von Eric beschriebenen Muster vertraut klingen. Powershell-Cmdlets sind Monaden; Die funktionale Zusammensetzung wird durch eine Pipeline dargestellt .
Jeffrey Snovers Interview mit Erik Meijer geht detaillierter.
quelle
Siehe meine Antwort auf "Was ist eine Monade?"
Es beginnt mit einem motivierenden Beispiel, arbeitet das Beispiel durch, leitet ein Beispiel für eine Monade ab und definiert formal "Monade".
Es setzt keine Kenntnisse der funktionalen Programmierung voraus und verwendet Pseudocode mit
function(argument) := expression
Syntax mit möglichst einfachen Ausdrücken.Dieses C ++ - Programm ist eine Implementierung der Pseudocode-Monade. (Als Referenz:
M
Ist der Typkonstruktor,feed
ist die "Bind" -Operation undwrap
ist die "Return" -Operation.)quelle
Aus praktischer Sicht (zusammenfassend, was in vielen früheren Antworten und verwandten Artikeln gesagt wurde) scheint es mir, dass einer der grundlegenden "Zwecke" (oder Nützlichkeit) der Monade darin besteht, die Abhängigkeiten zu nutzen, die mit rekursiven Methodenaufrufen verbunden sind aka Funktionszusammensetzung (dh wenn f1 f2 aufruft, muss f3 vor f2 vor f1 ausgewertet werden), um die sequentielle Komposition auf natürliche Weise darzustellen, insbesondere im Kontext eines faulen Bewertungsmodells (dh der sequentiellen Komposition als einfache Sequenz) , zB "f3 (); f2 (); f1 ();" in C - der Trick ist besonders offensichtlich, wenn Sie an einen Fall denken, in dem f3, f2 und f1 tatsächlich nichts zurückgeben [ihre Verkettung als f1 (f2 (f3)) ist künstlich, nur zur Erzeugung von Sequenzen gedacht]).
Dies ist besonders relevant, wenn Nebenwirkungen beteiligt sind, dh wenn ein Zustand geändert wird (wenn f1, f2, f3 keine Nebenwirkungen hatten, spielt es keine Rolle, in welcher Reihenfolge sie bewertet werden; was eine großartige Eigenschaft von pure ist funktionale Sprachen, um diese Berechnungen beispielsweise parallelisieren zu können). Je mehr reine Funktionen, desto besser.
Ich denke, unter diesem engen Gesichtspunkt könnten Monaden als syntaktischer Zucker für Sprachen angesehen werden, die eine träge Bewertung bevorzugen (die Dinge nur dann bewerten, wenn dies unbedingt erforderlich ist, und zwar in einer Reihenfolge, die nicht auf der Darstellung des Codes beruht), und die keine haben andere Mittel zur Darstellung der sequentiellen Zusammensetzung. Das Nettoergebnis ist, dass Codeabschnitte, die "unrein" sind (dh Nebenwirkungen haben), auf natürliche Weise auf zwingende Weise dargestellt werden können, jedoch sauber von reinen Funktionen (ohne Nebenwirkungen) getrennt sind, die es sein können träge ausgewertet.
Dies ist jedoch nur ein Aspekt, wie hier gewarnt .
quelle
Die einfachste Erklärung, die ich mir vorstellen kann, ist, dass Monaden eine Möglichkeit sind, Funktionen mit verschönerten Ergebnissen zu komponieren (auch bekannt als Kleisli-Komposition). Eine "verschönerte" Funktion hat die Signatur,
a -> (b, smth)
woa
undb
sind Typen (denkenInt
,Bool
), die sich möglicherweise voneinander unterscheiden, aber nicht unbedingt - undsmth
ist der "Kontext" oder die "Verschönerung".Diese Art von Funktionen kann auch dort geschrieben werden,
a -> m b
wom
sie der "Verschönerung" entsprechensmth
. Dies sind also Funktionen, die Werte im Kontext zurückgeben (denken Sie an Funktionen, die ihre Aktionen protokollieren, wosmth
sich die Protokollierungsnachricht befindet, oder an Funktionen, die Eingabe / Ausgabe ausführen, und deren Ergebnisse hängen vom Ergebnis der E / A-Aktion ab).Eine Monade ist eine Schnittstelle ("Typklasse"), über die der Implementierer erklärt, wie solche Funktionen zusammengesetzt werden sollen. Der Implementierer muss eine Kompositionsfunktion
(a -> m b) -> (b -> m c) -> (a -> m c)
für jeden Typ definierenm
, der die Schnittstelle implementieren möchte (dies ist die Kleisli-Komposition).Also, wenn wir sagen , dass wir einen Tupel Typen haben
(Int, String)
repräsentiert Ergebnisse von Berechnungen aufInt
s , die auch ihre Aktionen einzuloggen, mit(_, String)
als das „embelishment“ - das Protokoll der Aktion - und zwei Funktionenincrement :: Int -> (Int, String)
undtwoTimes :: Int -> (Int, String)
wir wollen eine Funktion erhalten ,incrementThenDouble :: Int -> (Int, String)
die die Zusammensetzung der beiden Funktionen, die auch die Protokolle berücksichtigen.In dem gegebenen Beispiel würde eine Monadenimplementierung der beiden Funktionen auf den ganzzahligen Wert 2
incrementThenDouble 2
(der gleich isttwoTimes (increment 2)
) angewendet, der(6, " Adding 1. Doubling 3.")
für Zwischenergebnisseincrement 2
gleich(3, " Adding 1.")
undtwoTimes 3
gleich zurückgeben würde(6, " Doubling 3.")
Aus dieser Kleisli-Kompositionsfunktion lassen sich die üblichen monadischen Funktionen ableiten.
quelle