Monade im Klartext? (Für den OOP-Programmierer ohne FP-Hintergrund)


743

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?




10
@Pavel: Die Antwort, die wir unten von Eric erhalten haben, ist viel besser als die in den anderen vorgeschlagenen Fragen für Leute mit einem OO-Hintergrund (im Gegensatz zu einem FP-Hintergrund).
Donal Fellows

5
@Donal: Wenn dies ist eine Betrogene (über die ich habe keine Meinung), sollte die gute Antwort auf die ursprüngliche hinzugefügt werden. Das heißt: Eine gute Antwort schließt das Schließen als Duplikat nicht aus. Wenn es sich um ein ausreichend nahes Duplikat handelt, kann dies von einem Moderator als Zusammenführung durchgeführt werden.
dmckee --- Ex-Moderator Kätzchen

Antworten:


732

UPDATE: Diese Frage war Gegenstand einer immens langen Blogserie, die Sie bei Monads lesen können - danke für die tolle Frage!

Was ist eine Monade, die ein OOP-Programmierer verstehen würde (ohne funktionalen Programmierhintergrund)?

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 nehmen intund 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 nehmen stringund 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

int M(int x) { return x + N(x * 2); }

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"?

  1. 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.

  2. 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.

  3. 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 ein intin ein verwandeln Nullable<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,

int M(int x) { whatever }

verwandelt sich in

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

Und mit der Immobilie wird aus einem Nullable<int>Rücken ein Rücken intgemacht Value.

Es ist die Funktionstransformation, die das Schlüsselbit ist. Beachten Sie, wie die tatsächliche Semantik der nullbaren Operation - dass eine Operation auf einer nullpropagiert null- in der Transformation erfasst wird. Wir können das verallgemeinern.

Angenommen, Sie haben eine Funktion von intbis int, wie unser Original M. Sie können dies leicht zu einer Funktion machen, die a annimmt intund 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:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

Sehen Sie, was Sie damit machen können? Auf jede Methode, die ein intund ein zurückgibt intoder ein intund ein zurückgibt, Nullable<int>kann jetzt die nullbare Semantik angewendet werden .

Außerdem: Angenommen, Sie haben zwei Methoden

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

und du willst sie komponieren:

Nullable<int> Z(int s) { return X(Y(s)); }

Das heißt, Zist die Zusammensetzung von Xund Y. Aber Sie können das nicht tun, weil Sie a Xnehmen intund Ya zurückgeben Nullable<int>. Da Sie jedoch die "Bind" -Operation haben, können Sie Folgendes ausführen:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

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:

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

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/ SelectManyMethoden 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:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

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.

Welches Problem löst es und an welchen Orten wird es am häufigsten verwendet?

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:

  • Ich muss neue Funktionen für diesen Typ erstellen und dennoch alte Funktionen für diesen Typ kombinieren, um die neuen Funktionen nutzen zu können.
  • Ich muss eine Reihe von Operationen für Typen erfassen und diese Operationen als zusammensetzbare Objekte darstellen, immer größere Kompositionen aufbauen, bis genau die richtige Reihe von Operationen dargestellt ist, und dann muss ich anfangen, Ergebnisse aus der Sache herauszuholen
  • Ich muss Nebenwirkungen sauber in einer Sprache darstellen, die Nebenwirkungen hasst

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 SelectManyMethode 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önnte SelectMany; alles andere ist nur eine Annehmlichkeit.)

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 in die OOP-App zu portieren?

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 SelectManyMethode; 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:

  1. Für eine ausführlichere und theoretisch fundiertere Erklärung der Monaden in C # empfehle ich den Artikel meines Kollegen Eric Dyer ( Eric Lippert ) zu diesem Thema. Dieser Artikel hat mir Monaden erklärt, als sie endlich für mich "geklickt" haben.
  2. Ein gutes Beispiel dafür, warum Sie vielleicht eine Monade haben möchten (verwendet Haskell in seinen Beispielen) .
  3. Art, "Übersetzung" des vorherigen Artikels in JavaScript.


17
Dies ist eine großartige Antwort, aber mein Kopf ging schief. Ich werde dieses Wochenende nachverfolgen und es anstarren und Ihnen Fragen stellen, wenn sich die Dinge nicht beruhigen und in meinem Kopf einen Sinn ergeben.
Paul Nathan

5
Hervorragende Erklärung wie immer Eric. Für eine theoretischere (aber immer noch hochinteressante) Diskussion fand ich Bart De Smets Blog-Beitrag auf MinLINQ hilfreich, um einige funktionale Programmierkonstrukte auch wieder mit C # in Verbindung zu bringen. community.bartdesmet.net/blogs/bart/archive/2010/01/01/…
Ron Warholic

41
Es macht mir mehr Sinn zu sagen , dass es vermehrt Typen anstatt verstärkt sie.
Gabe

61
@slomojo: und ich habe es wieder auf das geändert, was ich geschrieben habe und schreiben wollte. Wenn Sie und Gabe Ihre eigene Antwort schreiben möchten, fahren Sie fort.
Eric Lippert

24
@Eric, natürlich bis zu dir, aber Amplifier impliziert, dass vorhandene Eigenschaften verbessert werden, was irreführend ist.
ocodo

341

Warum brauchen wir Monaden?

  1. Wir wollen nur mit Funktionen programmieren . ("funktionale Programmierung" immerhin -FP).
  2. 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 gund dann wollen f, schreiben Sie einfach f(g(x,y)). OK aber ...

  3. 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 uns g : Real,Real -> Real | Nothing(Funktion von zwei Real in (Real oder nichts)) zulassen .

  4. 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 ...

  5. Was passiert jetzt mit f(g(x,y))? fist nicht bereit zu konsumieren a Maybe Real. Und wir möchten nicht jede Funktion ändern, mit der wir uns verbinden können g, um a zu konsumieren Maybe 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 / komponieren gmit f). Wir möchten >>=die gAusgabe Nothingabrufen , sie überprüfen und, falls dies nicht der Fall ist , nicht anrufen fund zurückgeben Nothing. oder im Gegenteil, extrahieren Sie die Box Realund füttern fSie damit. (Dieser Algorithmus ist nur die Implementierung >>=für den MaybeTyp).

  6. 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 gsolche Funktionen diese "Box-Werte" zurückgeben. 2. Lassen Sie Komponisten / Linker g >>= fhelfen g, die Ausgabe mit fder Eingabe zu verbinden, damit wir uns überhaupt nicht ändern müssen f.

  7. 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 !!!!


2
@DmitriZaitsev Ausnahmen können meines Wissens nur in "unreinem Code" (der E / A-Monade) auftreten.
Cibercitizen1

3
@DmitriZaitsev Die Rolle von Nothing kann von jedem anderen Typ gespielt werden (anders als der erwartete Real). Das ist nicht der Punkt. Im Beispiel geht es darum, wie Funktionen in einer Kette angepasst werden können, wenn die vorherige möglicherweise einen unerwarteten Werttyp an den folgenden zurückgibt, ohne diesen zu verketten (wobei nur ein Real als Eingabe akzeptiert wird).
Cibercitizen1

3
Ein weiterer Verwirrungspunkt ist, dass das Wort "Monade" in Ihrer Antwort nur zweimal vorkommt und nur in Kombination mit anderen Begriffen - Stateund IOmit keinem von ihnen sowie der genauen Bedeutung von "Monade"
Dmitri Zaitsev

31
Für mich als Person mit OOP-Hintergrund erklärte diese Antwort wirklich gut die Motivation, eine Monade zu haben, und auch, was die Monade tatsächlich ist (viel mehr als eine akzeptierte Antwort). Ich finde es sehr hilfreich. Vielen Dank @ cibercitizen1 und +1
akhilless

3
Ich lese seit ungefähr einem Jahr immer wieder über funktionale Programmierung. Diese Antwort und insbesondere die ersten beiden Punkte haben mir endlich klar gemacht, was imperative Programmierung eigentlich bedeutet und warum funktionale Programmierung anders ist. Vielen Dank!
Jrahhali

82

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.


15
Ich glaube, dies ist die erste Monadenerklärung, die ich gesehen habe und die sich nicht auf funktionale Programmierkonzepte stützte und sie in echte OOP-Begriffe umwandelte. Wirklich gute Antwort.
David K. Hess

Dies ist sehr nahe 2, was Monaden tatsächlich in FP / Haskell sind, außer dass die Befehlsobjekte selbst "wissen", zu welcher "Aufruflogik" sie gehören (und nur die kompatiblen miteinander verkettet werden können); Der Aufrufer liefert nur den ersten Wert. Es ist nicht so, dass der Befehl "Drucken" von der "nicht deterministischen Ausführungslogik" ausgeführt werden kann. Nein, es muss "E / A-Logik" sein (dh E / A-Monade). Aber anders als das ist es sehr nah. Man könnte sogar einfach sagen, dass Monaden nur Programme sind (aufgebaut aus Code-Anweisungen, die später ausgeführt werden sollen). In den frühen Tagen wurde "binden" als "programmierbares Semikolon" bezeichnet .
Will Ness

1
@ DavidK.Hess Ich bin in der Tat unglaublich skeptisch gegenüber Antworten, die FP verwenden, um grundlegende FP-Konzepte zu erklären, und insbesondere Antworten, die eine FP-Sprache wie Scala verwenden. Gut gemacht, JacquesB!
Stellen Sie Monica am

62

Was ist eine Monade, die ein OOP-Programmierer verstehen würde (ohne funktionalen Programmierhintergrund)?

Welches Problem löst es und welche Orte werden am häufigsten verwendet? Werden sie am häufigsten verwendet?

In Bezug auf die OO-Programmierung ist eine Monade eine Schnittstelle (oder eher ein Mixin), die durch einen Typ mit zwei Methoden parametrisiert wird returnund bindFolgendes beschreibt:

  • Wie man einen Wert einfügt, um einen monadischen Wert dieses injizierten Werttyps zu erhalten;
  • Verwendung einer Funktion, die einen monadischen Wert aus einem nicht monadischen Wert auf einen monadischen Wert setzt.

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" ähnlich IEnumeratoroder IIteratordahingehend, dass sie einen Typ annimmt, der selbst einen Typ annimmt. Der Hauptpunkt dabei Monadist 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.


1
returnwäre eigentlich keine Methode für die Monade, da keine Monadeninstanz als Argument verwendet wird. (dh: es gibt kein dies / selbst)
Laurence Gonsalves

@LaurenceGonsalves: Da ich dies derzeit für meine Bachelorarbeit untersuche, denke ich, dass das Fehlen statischer Methoden in Schnittstellen in C # / Java die größte Einschränkung darstellt. Sie könnten einen weiten Weg in Richtung der Implementierung der gesamten Monadengeschichte gehen, zumindest statisch gebunden, anstatt auf Typklassen zu basieren. Interessanterweise würde dies sogar funktionieren, obwohl es keine höherwertigen Typen gibt.
Sebastian Graf

42

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).

Ein Typ - Konstruktor M ist eine Monade , wenn es diese Operationen unterstützt:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

So zum Beispiel (in Scala):

  • Option ist eine Monade
    def Einheit [A] (x: A): Option [A] = Einige (x)

    def flatMap [A, B] (m: Option [A]) (f: A => Option [B]): Option [B] =
      m Übereinstimmung {
       case None => None
       case Einige (x) => f (x)
      }}
  • List ist Monade
    def Einheit [A] (x: A): Liste [A] = Liste (x)

    def flatMap [A, B] (m: Liste [A]) (f: A => Liste [B]): Liste [B] =
      m Übereinstimmung {
        case Nil => Nil
        case x :: xs => f (x) ::: flatMap (xs) (f)
      }}

Monaden sind eine große Sache in Scala, da die Syntax so gestaltet ist, dass sie die Vorteile von Monadenstrukturen nutzt:

forVerständnis in Scala :

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

wird vom Compiler übersetzt in:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

Die Schlüsselabstraktion ist die flatMap, die die Berechnung durch Verkettung bindet.
Jeder Aufruf von flatMapgibt 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ück List[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.

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

Aber ohne Monad auszunutzen, erhalten Sie verschlungenen OOP-Code wie:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

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:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

Der Yield-Teil wird nur ausgeführt, wenn alle drei Funktionen haben Some[X]; Jeder Nonewürde direkt an zurückgegeben werden confirm.


Damit:

Monaden ermöglichen eine geordnete Berechnung innerhalb der funktionalen Programmierung, die es uns ermöglicht, die Sequenzierung von Aktionen in einer schön strukturierten Form zu modellieren, ähnlich wie bei einer DSL.

Die größte Kraft liegt in der Fähigkeit, Monaden, die unterschiedlichen Zwecken dienen, zu erweiterbaren Abstraktionen innerhalb einer Anwendung zusammenzusetzen.

Diese Sequenzierung und das Threading von Aktionen durch eine Monade erfolgt durch den Sprachcompiler, der die Transformation durch die Magie von Abschlüssen durchführt.


Monad ist übrigens nicht nur ein Berechnungsmodell, das in FP verwendet wird:

Die Kategorietheorie schlägt viele Berechnungsmodelle vor. Darunter

  • das Pfeilmodell der Berechnungen
  • das Monadenmodell der Berechnungen
  • das anwendbare Modell der Berechnungen

2
Ich liebe diese Erklärung! Das Beispiel, das Sie gegeben haben, demonstriert das Konzept auf wundervolle Weise und fügt hinzu, was IMHO in Erics Teaser über SelectMany () als Monade fehlte. Danke dafür!
Aoven

1
IMHO ist dies die eleganteste Antwort
Polymerase

und vor allem Functor.
Will Ness

34

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:

Eine Monade (in der Informatik) ist formal eine Karte, die:

  • sendet jeden Typ Xeiner bestimmten Programmiersprache an einen neuen Typ T(X)(der als "Typ von TBerechnungen mit Werten in X" bezeichnet wird);

  • ausgestattet mit einer Regel zum Zusammensetzen von zwei Funktionen des Formulars f:X->T(Y)und g:Y->T(Z)einer Funktion g∘f:X->T(Z);

  • auf eine Weise, die im offensichtlichen Sinne assoziativ und in Bezug auf eine gegebene Einheitsfunktion unital pure_X:X->T(X)ist, die als Wert für die reine Berechnung angesehen wird, die diesen Wert einfach zurückgibt.

In einfachen Worten, eine Monade ist eine Regel, die von einem beliebigen Typ Xan einen anderen Typ übergeben werden mussT(X) , und eine Regel, die von zwei Funktionen f:X->T(Y)und g: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:

  • Assoziativität : Das Komponieren fmit gund dann mit h(von außen) sollte dasselbe sein wie das Komponieren gmit hund dann mit f(von innen).
  • Unitales Eigentum : Das Komponieren fmit der Identitätsfunktion auf beiden Seiten sollte ergeben f.

Mit einfachen Worten, wir können nicht einfach verrückt werden, wenn wir unsere Funktionszusammensetzung neu definieren, wie wir möchten:

  • Wir brauchen zuerst die Assoziativität, um mehrere Funktionen in einer Reihe zusammensetzen zu können, z. B. 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, fmit gder gzusammengesetzt wurde f, die mit der zusammengesetzten Eigenschaft identisch ist , was nicht erforderlich ist.)
  • Und zweitens brauchen wir das einheitliche Eigentum, das heißt einfach, dass Identitäten trivial so zusammengesetzt sind, wie wir sie erwarten. So können wir Funktionen sicher umgestalten, wenn diese Identitäten extrahiert werden können.

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, hdie "Komposition" von fund zu nennen g. Weil es mathematisch nicht so ist. Wenn man es die "Komposition" nennt, hwird fälschlicherweise davon ausgegangen, dass es sich um die wahre mathematische Komposition handelt, die es nicht ist. Es wird nicht einmal eindeutig von fund bestimmt g. 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:

f: x -> 1 / x
g: y -> 2 * y

Ist f(0)aber nicht definiert, wird eine Ausnahme eausgelöst. Wie können Sie dann den Kompositionswert definieren g(f(0))? Wirf natürlich wieder eine Ausnahme! Vielleicht das gleiche e. Möglicherweise eine neue aktualisierte Ausnahme e1.

Was genau passiert hier? Erstens benötigen wir neue Ausnahmewerte (unterschiedlich oder gleich). Sie können sie nothingoder nullwas auch immer nennen, aber die Essenz bleibt dieselbe - sie sollten neue Werte sein, z. B. sollte es numberin unserem Beispiel hier kein sein . Ich ziehe es vor, sie nicht anzurufen null, um Verwechslungen mit nullder Implementierung in einer bestimmten Sprache zu vermeiden . Ebenso bevorzuge ich es zu vermeiden, nothingweil es oft damit verbunden ist null, was im Prinzip zu nulltun 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 NaNoder null) 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, findem wir 0an den neuen abstrakten Wert senden , den ewir Ausnahme nennen. Wir stellen sicher, dass der Wert ekeine externen Informationen enthält, die nicht eindeutig durch unsere Eingabe bestimmt werden x. Hier ist ein Beispiel für eine Ausnahme ohne Nebenwirkung:

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

Und hier ist einer mit Nebeneffekt:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

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 42jemals zurückkehrt , ist eindeutig rein. Aber wenn jemand Verrückter beschließt, 42eine 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 errores 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 verhalten nulloder NaNnicht 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 Efür das gesamte Ausnahmeobjekt und dann ist es das maybe number, was das tut, abgesehen von seinem verwirrenden Namen, der entweder vom Typ numberoder vom neuen Ausnahmetyp sein soll EEs ist also wirklich die Vereinigung number | Evon numberund E. Insbesondere hängt es davon ab, wie wir konstruieren wollen E, was im Namen weder vorgeschlagen noch reflektiert wird maybe number.

Was ist funktionale Zusammensetzung?

Es ist die mathematische Operation Mitnahmen Funktionen f: X -> Yund g: Y -> Zund deren Zusammensetzung als Funktion der Konstruktion h: X -> Zerfüllen h(x) = g(f(x)). Das Problem mit dieser Definition tritt auf, wenn das Ergebnis f(x)als Argument von nicht zulässig ist g.

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 fund gbesteht darin, 0aus dem Definitionssatz von zu entfernen f. Mit dieser neuen Definition (neue restriktivere Art von x) fwird mit zusammensetzbar g.

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, Infinityetc. So können Sie bewerten 1/0zu Infinityund 1/-0zu -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:

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

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 ffür einige eine Ausnahme ausgelöst wird x, dies auch für die Zusammensetzung bei einigen gilt g. Machen Sie die Ausnahme Emit 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 .


3
Guter Beitrag. +1 Aber vielleicht möchten Sie löschen, dass "die meisten Erklärungen zu lange gefunden haben ..." Ihre längste ist. Andere werden beurteilen, ob es sich bei Bedarf um "einfaches Englisch" handelt: "einfaches Englisch == in einfachen Worten, auf einfache Weise".
Cibercitizen1

@ cibercitizen1 Danke! Es ist eigentlich kurz, wenn Sie das Beispiel nicht zählen. Der Hauptpunkt ist, dass Sie das Beispiel nicht lesen müssen, um die Definition zu verstehen . Leider zwingen mich viele Erklärungen , zuerst Beispiele zu lesen , was oft unnötig ist, aber natürlich zusätzliche Arbeit für den Autor erfordert. Bei zu starkem Vertrauen in bestimmte Beispiele besteht die Gefahr, dass unwichtige Details das Bild verdecken und das Erfassen erschweren. Trotzdem haben Sie gültige Punkte, siehe das Update.
Dmitri Zaitsev

2
zu lang und verwirrend
gesehenimurugan

1
@seenimurugan Verbesserungsvorschläge sind willkommen;)
Dmitri Zaitsev

26

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 kapselt x
  • m >>= f(Lesen Sie es als "Bindungsoperator") Wendet die Funktion fauf 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 returnund haben >>=.


"Was eine Monade tut, und das hängt von der Monade ab": und genauer gesagt, das hängt von der bindFunktion 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.
Hibou57

14

Aus Wikipedia :

In der funktionalen Programmierung ist eine Monade eine Art abstrakter Datentyp, der zur Darstellung von Berechnungen verwendet wird (anstelle von Daten im Domänenmodell). Mit Monaden kann der Programmierer Aktionen verketten, um eine Pipeline zu erstellen, in der jede Aktion mit zusätzlichen Verarbeitungsregeln versehen ist, die von der Monade bereitgestellt werden. In funktionalem Stil geschriebene Programme können Monaden verwenden, um Prozeduren zu strukturieren, die sequenzierte Operationen 1 [2] enthalten, oder um beliebige Kontrollflüsse zu definieren (wie die Behandlung von Parallelität, Fortsetzungen oder Ausnahmen).

Formal wird eine Monade konstruiert, indem zwei Operationen (Binden und Zurückgeben) und ein Typkonstruktor M definiert werden, die mehrere Eigenschaften erfüllen müssen, um die korrekte Zusammensetzung monadischer Funktionen zu ermöglichen (dh Funktionen, die Werte aus der Monade als Argumente verwenden). Die Rückgabeoperation nimmt einen Wert von einem einfachen Typ und legt ihn in einem monadischen Container vom Typ M ab. Die Bindungsoperation führt den umgekehrten Prozess aus, extrahiert den ursprünglichen Wert aus dem Container und übergibt ihn an die zugehörige nächste Funktion in der Pipeline.

Ein Programmierer erstellt monadische Funktionen, um eine Datenverarbeitungspipeline zu definieren. Die Monade fungiert als Framework, da es sich um ein wiederverwendbares Verhalten handelt, das die Reihenfolge bestimmt, in der die spezifischen monadischen Funktionen in der Pipeline aufgerufen werden, und alle für die Berechnung erforderlichen Undercover-Arbeiten verwaltet. [3] Die in der Pipeline verschachtelten Bindungs- und Return-Operatoren werden ausgeführt, nachdem jede monadische Funktion die Steuerung zurückgegeben hat, und kümmern sich um die besonderen Aspekte, die von der Monade behandelt werden.

Ich glaube, das erklärt es sehr gut.


12

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:

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

und wenn die folgenden Gesetze für alle Typen T und ihre möglichen Werte t gelten

linke Identität:

CMonadic<T>.create(t).flatMap(f) == f(t)

richtige Identität

instance.flatMap(CMonadic<T>.create) == instance

Assoziativität:

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

Beispiele :

Eine Listenmonade kann Folgendes haben:

List<int>.create(1) --> [1]

Und flatMap auf der Liste [1,2,3] könnte folgendermaßen funktionieren:

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Iterables und Observables können ebenso monadisch gemacht werden wie Versprechen und Aufgaben.

Kommentar :

Monaden sind nicht so kompliziert. Die flatMapFunktion ist der am häufigsten anzutreffenden sehr ähnlich map. 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ützen create, 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:

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

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:

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}

7

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:

  • Listen (nicht deterministische Berechnungen, indem eine Liste als Domäne behandelt wird)
  • Möglicherweise (Berechnungen, die fehlschlagen können, für die die Berichterstellung jedoch unwichtig ist)
  • Fehler (Berechnungen, die fehlschlagen können und eine Ausnahmebehandlung erfordern
  • Reader (Berechnungen, die durch Kompositionen einfacher Haskell-Funktionen dargestellt werden können)
  • Writer (Berechnungen mit sequentiellem "Rendern" / "Protokollieren" (zu Strings, HTML usw.)
  • Fortsetzung (Fortsetzung)
  • IO (Berechnungen, die vom zugrunde liegenden Computersystem abhängen)
  • Zustand (Berechnungen, deren Kontext einen veränderbaren Wert enthält)

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

instance MonadState s (State s) where
    put = ...
    get = ...

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:

return :: a -> m a

ist eine Funktion, die einen Wert vom Typ a in eine Monaden- "Aktion" vom Typ m a einfügt.

(>>=) :: m a -> (a -> m b) -> m b

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:

m >>= f >>= g

Oder mit "Notation machen"

do x <- m
   y <- f x
   g y

Der Typ für (>>) leuchtet möglicherweise. Es ist

(>>) :: m a -> m b -> m b

Es entspricht dem Operator (;) in prozeduralen Sprachen wie C. Es erlaubt Notationen wie:

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

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.

join :: m (m a) -> m a

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

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

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):

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

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


3

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.

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

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

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

Und es würde so verwendet werden:

print (runMyMonad (Just 160) myArray1)

1
Super ordentlich! Binden ist also nur eine Möglichkeit, eine Reihe von Funktionen mit Kontext nacheinander auf einer Eingabe mit Kontext
auszuwerten

>>=ist ein Betreiber
user2418306

1
Ich denke, die Analogie "Funktionsumfang" klärt nicht viel. Wenn \x -> x >>= k >>= l >>= mes sich um eine Reihe von Funktionen handelt, handelt h . g . fes sich auch nicht um Monaden.
Duplode

Wir könnten sagen, dass es bei Funktoren , ob monadisch, anwendungsbezogen oder einfach, um "verschönerte Anwendung" geht . 'applikativ' fügt Verkettung hinzu, und 'Monade' fügt Abhängigkeit hinzu (dh das Erstellen des nächsten Berechnungsschritts in Abhängigkeit von den Ergebnissen eines vorherigen Berechnungsschritts).
Will Ness

3

In OO-Begriffen ist eine Monade ein fließender Behälter.

Die Mindestanforderung ist eine Definition class <A> Something, die einen Konstruktor Something(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ür Optional<String>, Optional<Integer>,Optional<MyClass> etc.

Als grobes Beispiel:

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

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.


2

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.


2

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

  1. Die unten stehende Funktionszusammensetzung ist transitiv. Wenn Sie also von A nach CI wechseln, können Sie A => B und B => C zusammensetzen.
 A => C   =   A => B  andThen  B => C

Geben Sie hier die Bildbeschreibung ein

  1. 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].
    Geben Sie hier die Bildbeschreibung ein

    Wir brauchen einen speziellen Operator, "bind", der weiß, wie man diese Funktionen zusammenführt, die F [A] zurückgeben.

 A => F[C]   =   A => F[B]  bind  B => F[C]

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 ,

 A => F[B]   =   A => B  andThen  return

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

  • "zufällig" ( Bereich => zufällig [Int] )
  • "print" ( String => IO [()] )
  • "try ... catch" usw.

2

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.



1

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) := expressionSyntax mit möglichst einfachen Ausdrücken.

Dieses C ++ - Programm ist eine Implementierung der Pseudocode-Monade. (Als Referenz: MIst der Typkonstruktor, feedist die "Bind" -Operation und wrapist die "Return" -Operation.)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}

0

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 .


0

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)wo aund bsind Typen (denken Int, Bool), die sich möglicherweise voneinander unterscheiden, aber nicht unbedingt - und smthist der "Kontext" oder die "Verschönerung".

Diese Art von Funktionen kann auch dort geschrieben werden, a -> m bwo msie der "Verschönerung" entsprechen smth. Dies sind also Funktionen, die Werte im Kontext zurückgeben (denken Sie an Funktionen, die ihre Aktionen protokollieren, wo smthsich 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 definieren m, 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 auf Ints , die auch ihre Aktionen einzuloggen, mit (_, String)als das „embelishment“ - das Protokoll der Aktion - und zwei Funktionen increment :: Int -> (Int, String)und twoTimes :: 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 ist twoTimes (increment 2)) angewendet, der (6, " Adding 1. Doubling 3.")für Zwischenergebnisse increment 2gleich (3, " Adding 1.")und twoTimes 3gleich zurückgeben würde(6, " Doubling 3.")

Aus dieser Kleisli-Kompositionsfunktion lassen sich die üblichen monadischen Funktionen ableiten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.