Was ist eine Monade?

1415

Was wäre eine kurze, prägnante und praktische Erklärung, was eine Monade im Wesentlichen ist , nachdem sie sich kürzlich kurz mit Haskell befasst hat?

Ich habe festgestellt, dass die meisten Erklärungen, auf die ich gestoßen bin, ziemlich unzugänglich sind und keine praktischen Details enthalten.

Peter Mortensen
quelle
12
Eric Lippert hat eine Antwort auf diese Fragen geschrieben ( stackoverflow.com/questions/2704652/… ), die aufgrund einiger Probleme auf einer separaten Seite zu finden sind.
P Shved
70
Hier ist eine neue Einführung mit Javascript - ich fand es sehr lesbar.
Benjol
20
Siehe auch Monaden in Bildern
cibercitizen1
2
Eine Monade ist eine Reihe von Funktionen mit Hilfsoperationen. Siehe diese Antwort
cibercitizen1

Antworten:

1059

Erstens: Der Begriff Monade ist etwas leer, wenn Sie kein Mathematiker sind. Ein alternativer Begriff ist Computation Builder, der etwas aussagekräftiger ist, wofür sie tatsächlich nützlich sind.

Sie fragen nach praktischen Beispielen:

Beispiel 1: Listenverständnis :

[x*2 | x<-[1..10], odd x]

Dieser Ausdruck gibt das Doppel aller ungeraden Zahlen im Bereich von 1 bis 10 zurück. Sehr nützlich!

Es stellt sich heraus, dass dies für einige Operationen innerhalb der List-Monade wirklich nur syntaktischer Zucker ist. Das gleiche Listenverständnis kann geschrieben werden als:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

Oder auch:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Beispiel 2: Eingabe / Ausgabe :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

Beide Beispiele verwenden Monaden, AKA-Berechnungsgeneratoren. Das gemeinsame Thema ist, dass die Monadenketten auf eine bestimmte, nützliche Weise operieren . Im Listenverständnis sind die Operationen so verkettet, dass, wenn eine Operation eine Liste zurückgibt, die folgenden Operationen für jedes Element in der Liste ausgeführt werden. Die E / A-Monade hingegen führt die Operationen nacheinander aus, gibt jedoch eine "versteckte Variable" weiter, die den "Zustand der Welt" darstellt, wodurch wir E / A-Code auf rein funktionale Weise schreiben können.

Es stellt sich heraus, dass das Muster der Verkettungsvorgänge sehr nützlich ist und für viele verschiedene Dinge in Haskell verwendet wird.

Ein weiteres Beispiel sind Ausnahmen: Bei Verwendung der ErrorMonade werden Operationen so verkettet, dass sie nacheinander ausgeführt werden, es sei denn, es wird ein Fehler ausgegeben. In diesem Fall wird der Rest der Kette abgebrochen.

Sowohl die Syntax des Listenverständnisses als auch die Do-Notation sind syntaktischer Zucker für Verkettungsoperationen mit dem >>=Operator. Eine Monade ist im Grunde nur ein Typ, der den >>=Operator unterstützt .

Beispiel 3: Ein Parser

Dies ist ein sehr einfacher Parser, der entweder eine Zeichenfolge in Anführungszeichen oder eine Zahl analysiert:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

Die Operationen char, digitetc. sind ziemlich einfach. Sie stimmen entweder überein oder stimmen nicht überein. Die Magie ist die Monade, die den Kontrollfluss verwaltet: Die Operationen werden nacheinander ausgeführt, bis ein Match fehlschlägt. In diesem Fall wird die Monade auf die neueste zurückgesetzt <|>und versucht die nächste Option. Wieder eine Möglichkeit, Operationen mit einer zusätzlichen, nützlichen Semantik zu verketten.

Beispiel 4: Asynchrone Programmierung

Die obigen Beispiele sind in Haskell, aber es stellt sich heraus, dass F # auch Monaden unterstützt. Dieses Beispiel wurde Don Syme gestohlen :

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

Diese Methode ruft eine Webseite ab. Die Pointe wird verwendet von GetResponseAsync- sie wartet tatsächlich auf die Antwort in einem separaten Thread, während der Haupt-Thread von der Funktion zurückkehrt. Die letzten drei Zeilen werden auf dem erzeugten Thread ausgeführt, wenn die Antwort empfangen wurde.

In den meisten anderen Sprachen müssten Sie explizit eine separate Funktion für die Zeilen erstellen, die die Antwort verarbeiten. Die asyncMonade ist in der Lage, den Block selbst zu "teilen" und die Ausführung der zweiten Hälfte zu verschieben. (Die async {}Syntax gibt an, dass der Kontrollfluss im Block von der asyncMonade definiert wird .)

Wie sie arbeiten

Wie kann eine Monade all diese ausgefallenen Kontrollfluss-Dinge tun? Was tatsächlich in einem do-Block (oder einem Berechnungsausdruck, wie sie in F # genannt werden) passiert , ist, dass jede Operation (im Grunde jede Zeile) in eine separate anonyme Funktion eingeschlossen ist. Diese Funktionen werden dann mit dem bindOperator ( >>=in Haskell geschrieben) kombiniert . Da die bindOperation Funktionen kombiniert, kann sie sie nach Belieben ausführen: nacheinander mehrmals, in umgekehrter Reihenfolge, einige verwerfen, einige in einem separaten Thread ausführen, wenn es ihnen gefällt, und so weiter.

Dies ist beispielsweise die erweiterte Version des E / A-Codes aus Beispiel 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

Das ist hässlicher, aber es ist auch offensichtlicher, was tatsächlich vor sich geht. Der >>=Operator ist die magische Zutat: Er nimmt einen Wert (auf der linken Seite) und kombiniert ihn mit einer Funktion (auf der rechten Seite), um einen neuen Wert zu erzeugen. Dieser neue Wert wird dann vom nächsten >>=Operator übernommen und erneut mit einer Funktion kombiniert, um einen neuen Wert zu erzeugen. >>=kann als Mini-Evaluator angesehen werden.

Beachten Sie, dass dies >>=für verschiedene Typen überladen ist, sodass jede Monade ihre eigene Implementierung von hat >>=. (Alle Operationen in der Kette müssen jedoch vom Typ derselben Monade sein, sonst >>=funktioniert der Bediener nicht.)

Die einfachste mögliche Implementierung von >>=nimmt nur den Wert auf der linken Seite und wendet ihn auf die Funktion auf der rechten Seite an und gibt das Ergebnis zurück. Wie bereits erwähnt, ist das gesamte Muster nützlich, wenn in der Implementierung der Monade etwas Besonderes vor sich geht >>=.

Es gibt einige zusätzliche Klugheit bei der Übergabe der Werte von einer Operation zur nächsten, dies erfordert jedoch eine eingehendere Erläuterung des Haskell-Typsystems.

Zusammenfassen

In Haskell-Begriffen ist eine Monade ein parametrisierter Typ, der eine Instanz der Monad-Typklasse ist, die >>=zusammen mit einigen anderen Operatoren definiert wird. Für Laien ist eine Monade nur ein Typ, für den die >>=Operation definiert ist.

An sich >>=ist es nur eine umständliche Art, Funktionen zu verketten, aber mit der Do-Notation, die das "Sanitär" verbirgt, erweisen sich die monadischen Operationen als eine sehr schöne und nützliche Abstraktion, die an vielen Stellen in der Sprache nützlich und nützlich ist zum Erstellen eigener Mini-Sprachen in der Sprache.

Warum sind Monaden schwer?

Für viele Haskell-Lernende sind Monaden ein Hindernis, das sie wie eine Mauer treffen. Es ist nicht so, dass Monaden selbst komplex sind, sondern dass die Implementierung auf vielen anderen erweiterten Haskell-Funktionen wie parametrisierten Typen, Typklassen usw. beruht. Das Problem ist, dass Haskell I / O auf Monaden basiert und I / O wahrscheinlich eines der ersten Dinge ist, die Sie beim Erlernen einer neuen Sprache verstehen möchten - schließlich macht es nicht viel Spaß, Programme zu erstellen, die keine produzieren Ausgabe. Ich habe keine unmittelbare Lösung für dieses Henne-Ei-Problem, außer die Behandlung von E / A wie "Magie passiert hier", bis Sie genug Erfahrung mit anderen Teilen der Sprache haben. Es tut uns leid.

Ausgezeichneter Blog über Monaden: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

JacquesB
quelle
65
Als jemand, der große Probleme hatte, Monaden zu verstehen, kann ich sagen, dass diese Antwort geholfen hat ... ein wenig. Es gibt jedoch noch einige Dinge, die ich nicht verstehe. Inwiefern ist das Listenverständnis eine Monade? Gibt es eine erweiterte Form dieses Beispiels? Eine andere Sache, die mich an den meisten Monadenerklärungen wirklich stört, einschließlich dieser - Ist das, dass sie immer wieder verwechseln "Was ist eine Monade?" mit "Wofür ist eine Monade gut?" und "Wie wird eine Monade implementiert?". Sie haben diesen Hai gesprungen, als Sie geschrieben haben: "Eine Monade ist im Grunde nur ein Typ, der den Operator >> = unterstützt." Was mich gerade hatte ...
Breton
82
Ich bin auch nicht einverstanden mit Ihrer Schlussfolgerung, warum Monaden hart sind. Wenn Monaden selbst nicht komplex sind, sollten Sie in der Lage sein, zu erklären, was sie ohne ein paar Gepäckstücke sind. Ich möchte nichts über die Implementierung wissen, wenn ich die Frage "Was ist eine Monade?" Stellt. Ich möchte wissen, was es juckt, wenn es kratzt. Bisher scheint die Antwort zu lauten: "Weil die Autoren von Haskell Sadomasochisten sind und beschlossen haben, dass Sie etwas dumm Komplexes tun sollten, um einfache Dinge zu erreichen, MÜSSEN Sie Monaden lernen, um Haskell zu verwenden, und nicht, weil sie in irgendeiner Weise nützlich sind." selbst "...
Breton
69
Aber ... das kann doch nicht richtig sein, oder? Ich denke, Monaden sind schwierig, weil niemand herausfinden kann, wie man sie erklärt, ohne in verwirrende Implementierungsdetails verwickelt zu werden. Ich meine ... was ist ein Schulbus? Es ist eine Metallplattform mit einer Vorrichtung an der Vorderseite, die ein raffiniertes Erdölprodukt verbraucht, um in einem Zyklus einige Metallkolben anzutreiben, die wiederum eine Kurbelwelle drehen, die an einigen Zahnrädern befestigt ist, die einige Räder antreiben. Die Räder sind von aufgeblasenen Gummibeuteln umgeben, die mit einer Aschphaltoberfläche verbunden sind, um eine Ansammlung von Sitzen vorwärts zu bewegen. Die Sitze bewegen sich vorwärts, weil ...
Breton
130
Ich habe das alles gelesen und weiß immer noch nicht, was eine Monade ist, abgesehen von der Tatsache, dass es etwas ist, was Haskell-Programmierer nicht gut genug verstehen, um es zu erklären. Die Beispiele helfen nicht viel, da dies alles Dinge sind, die man ohne Monaden tun kann, und diese Antwort macht nicht klar, wie Monaden sie einfacher und nur verwirrender machen. Der eine Teil dieser Antwort, der beinahe nützlich war, war der, bei dem der syntaktische Zucker von Beispiel 2 entfernt wurde. Ich sage kam nahe, weil abgesehen von der ersten Zeile die Erweiterung keine wirkliche Ähnlichkeit mit dem Original hat.
Laurence Gonsalves
81
Ein weiteres Problem, das für Erklärungen von Monaden endemisch zu sein scheint, ist, dass es in Haskell geschrieben ist. Ich sage nicht, dass Haskell eine schlechte Sprache ist - ich sage, es ist eine schlechte Sprache, um Monaden zu erklären. Wenn ich Haskell kennen würde, würde ich Monaden bereits verstehen. Wenn Sie also Monaden erklären möchten, verwenden Sie zunächst eine Sprache, die Menschen, die Monaden nicht kennen, eher verstehen. Wenn Sie müssen Haskell verwenden, verwenden Sie nicht den syntaktischen Zucker überhaupt - mit der kleinsten, einfachstenen Teilmenge der Sprache Sie können und nicht über ein Verständnis von Haskell IO nehmen.
Laurence Gonsalves
712

"Was ist eine Monade?" Zu erklären ist ein bisschen wie "Was ist eine Zahl?" Wir verwenden ständig Zahlen. Aber stellen Sie sich vor, Sie haben jemanden getroffen, der nichts über Zahlen wusste. Wie zum Teufel würden Sie erklären, was Zahlen sind? Und wie würden Sie überhaupt beschreiben, warum das nützlich sein könnte?

Was ist eine Monade? Die kurze Antwort: Es ist eine spezielle Art, Operationen miteinander zu verketten.

Im Wesentlichen schreiben Sie Ausführungsschritte und verknüpfen sie mit der "Bindefunktion". (In Haskell heißt es >>=.) Sie können die Aufrufe selbst an den Bind-Operator schreiben oder Syntaxzucker verwenden, wodurch der Compiler diese Funktionsaufrufe für Sie einfügt. In beiden Fällen wird jeder Schritt durch einen Aufruf dieser Bindefunktion getrennt.

Die Bindungsfunktion ist also wie ein Semikolon. Es trennt die Schritte in einem Prozess. Die Aufgabe der Bindefunktion besteht darin, die Ausgabe aus dem vorherigen Schritt in den nächsten Schritt einzuspeisen.

Das klingt nicht zu schwer, oder? Aber es gibt mehr als eine Art von Monade. Warum? Wie?

Nun, die Bindefunktion kann nur das Ergebnis aus einem Schritt übernehmen und es dem nächsten Schritt zuführen. Aber wenn das "alles" ist, was die Monade tut ... ist das eigentlich nicht sehr nützlich. Und das ist wichtig , zu verstehen: Jede nützliche Monade tut etwas anderes zusätzlich nur eine Monade zu sein. Jede nützliche Monade hat eine "besondere Kraft", die sie einzigartig macht.

(Eine Monade, die nichts Besonderes tut , wird als "Identitätsmonade" bezeichnet. Ähnlich wie die Identitätsfunktion klingt dies wie eine völlig sinnlose Sache, stellt sich jedoch als nicht ... Aber das ist eine andere Geschichte ™.)

Grundsätzlich hat jede Monade ihre eigene Implementierung der Bindefunktion. Und Sie können eine Bindefunktion so schreiben, dass sie zwischen den Ausführungsschritten Fehler macht. Zum Beispiel:

  • Wenn jeder Schritt eine Erfolgs- / Fehleranzeige zurückgibt, können Sie den nächsten Schritt nur dann ausführen lassen, wenn der vorherige erfolgreich war. Auf diese Weise bricht ein fehlgeschlagener Schritt die gesamte Sequenz "automatisch" ab, ohne dass Sie einen bedingten Test durchführen müssen. (Die Fehlermonade .)

  • Wenn Sie diese Idee erweitern, können Sie "Ausnahmen" implementieren. (Die Fehlermonade oder Ausnahmemonade .) Da Sie sie selbst definieren und keine Sprachfunktion sind, können Sie definieren, wie sie funktionieren. (Vielleicht möchten Sie die ersten beiden Ausnahmen ignorieren und nur abbrechen, wenn eine dritte Ausnahme ausgelöst wird.)

  • Sie können dafür sorgen, dass jeder Schritt mehrere Ergebnisse zurückgibt und die Bindungsfunktionsschleife über ihnen liegt, wobei jedes für Sie in den nächsten Schritt eingespeist wird. Auf diese Weise müssen Sie nicht ständig Schleifen schreiben, wenn Sie mit mehreren Ergebnissen arbeiten. Die Bindefunktion "automatisch" erledigt das alles für Sie. (Die Listenmonade .)

  • Sie können nicht nur ein "Ergebnis" von einem Schritt zum anderen übergeben, sondern auch zusätzliche Daten an die Bindefunktion weitergeben . Diese Daten werden jetzt nicht in Ihrem Quellcode angezeigt, aber Sie können trotzdem von überall darauf zugreifen, ohne sie manuell an jede Funktion übergeben zu müssen. (Die Lesermonade .)

  • Sie können es so gestalten, dass die "zusätzlichen Daten" ersetzt werden können. Auf diese Weise können Sie destruktive Updates simulieren , ohne destruktive Updates durchzuführen. (Die Staatsmonade und ihr Cousin, die Schriftstellermonade .)

  • Da Sie nur destruktive Updates simulieren , können Sie trivial Dinge tun, die mit echten destruktiven Updates unmöglich wären . Sie können beispielsweise das letzte Update rückgängig machen oder zu einer älteren Version zurückkehren .

  • Sie können eine Monade erstellen , in der Berechnungen angehalten werden können , sodass Sie Ihr Programm anhalten, interne Statusdaten basteln und dann fortsetzen können.

  • Sie können "Fortsetzungen" als Monade implementieren. Dies ermöglicht es Ihnen, die Meinung der Menschen zu brechen!

All dies und mehr ist mit Monaden möglich. All dies ist natürlich auch ohne Monaden durchaus möglich . Mit Monaden ist es nur drastisch einfacher .

MathematicalOrchid
quelle
13
Ich schätze Ihre Antwort - insbesondere das endgültige Zugeständnis, dass all dies natürlich auch ohne Monaden möglich ist. Ein Punkt ist, dass es mit Monaden meistens einfacher ist, aber oft nicht so effizient wie ohne sie. Sobald Sie Transformatoren einbeziehen müssen, verursacht die zusätzliche Überlagerung von Funktionsaufrufen (und erstellten Funktionsobjekten) Kosten, die schwer zu erkennen und zu steuern sind und durch clevere Syntax unsichtbar gemacht werden.
seh
1
Zumindest in Haskell wird der größte Teil des Monadenaufwands vom Optimierer entfernt. Die einzigen wirklichen "Kosten" liegen also in der erforderlichen Gehirnleistung. (Dies ist nicht unerheblich, wenn Sie sich für "Wartbarkeit" interessieren.) Aber normalerweise machen Monaden die Dinge einfacher und nicht schwieriger. (Sonst, warum sollten Sie sich die Mühe machen?)
MathematicalOrchid
Ich bin nicht sicher, ob Haskell dies unterstützt oder nicht, aber mathematisch können Sie eine Monade entweder als >> = definieren und zurückgeben oder beitreten und ap. >> = und return machen Monaden praktisch nützlich, aber join und ap geben ein intuitiveres Verständnis dafür, was eine Monade ist.
Jeremy List
15
Diese Antwort kam aus einem nicht mathematischen, nicht funktionalen Programmierhintergrund und war für mich am sinnvollsten.
Jrahhali
10
Dies ist die erste Antwort, die mir tatsächlich eine Vorstellung davon gab, was zum Teufel eine Monade ist. Vielen Dank, dass Sie einen Weg gefunden haben, dies zu erklären!
Roboter
186

Entgegen dem allgemeinen Verständnis von Monaden haben sie eigentlich nichts mit Staat zu tun. Monaden sind einfach eine Möglichkeit, Dinge zu verpacken und Methoden bereitzustellen, um Operationen an den verpackten Sachen durchzuführen, ohne sie auszupacken.

Sie können beispielsweise in Haskell einen Typ erstellen, um einen anderen zu verpacken:

data Wrapped a = Wrap a

Um Dinge zu verpacken, die wir definieren

return :: a -> Wrapped a
return x = Wrap x

Um Vorgänge ohne Auspacken auszuführen, sagen wir, Sie haben eine Funktion f :: a -> b, dann können Sie dies tun, um zu heben Funktion , um auf umschlossene Werte zu reagieren:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

Das ist ungefähr alles, was es zu verstehen gibt. Es stellt sich jedoch heraus , dass es eine allgemeinere Funktion ist , dies zu tun Heben , das ist bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindkann ein bisschen mehr als fmap, aber nicht umgekehrt. Eigentlich fmapkann nur in Bezug auf bindund definiert werden return. Wenn Sie also eine Monade definieren, geben Sie ihren Typ an (hier war es Wrapped a) und sagen dann, wie ihre returnund bindOperationen funktionieren.

Das Coole ist, dass sich herausstellt, dass dies ein so allgemeines Muster ist, dass es überall auftaucht. Der reine Einkapselungszustand ist nur eines davon.

Einen guten Artikel darüber, wie Monaden verwendet werden können, um funktionale Abhängigkeiten einzuführen und damit die Reihenfolge der Auswertung zu steuern, wie sie in Haskells IO-Monade verwendet wird, finden Sie unter IO Inside .

Machen Sie sich keine Sorgen, wenn Sie Monaden verstehen wollen. Lesen Sie über sie, was Sie interessant finden, und machen Sie sich keine Sorgen, wenn Sie nicht sofort verstehen. Dann ist es der richtige Weg, nur in einer Sprache wie Haskell zu tauchen. Monaden sind eines dieser Dinge, bei denen das Verstehen durch Übung in Ihr Gehirn eindringt. Eines Tages merkt man plötzlich, dass man sie versteht.

Arnar
quelle
-> ist eine rechtsassoziative, spiegelnde Funktionsanwendung, die linksassoziativ ist. Das Auslassen der Klammern macht hier also keinen Unterschied.
Matthias Benkard
1
Ich denke nicht, dass dies eine sehr gute Erklärung ist. Monaden sind einfach ein Weg? Okay, in welche Richtung? Warum sollte ich nicht eine Klasse anstelle einer Monade verwenden?
Bretonischer
4
@ mb21: Wenn Sie nur darauf hinweisen, dass es zu viele Klammern gibt, beachten Sie, dass a-> b-> c eigentlich nur für a -> (b-> c) kurz ist. Das Schreiben dieses speziellen Beispiels als (a -> b) -> (Ta -> Tb) bedeutet streng genommen nur das Hinzufügen unnötiger Zeichen, aber es ist moralisch "das Richtige", da es betont, dass fmap eine Funktion vom Typ a -> abbildet b zu einer Funktion vom Typ Ta -> Tb. Und ursprünglich ist es das, was Funktoren in der Kategorietheorie tun, und daher kommen Monaden.
Nikolaj-K
1
Diese Antwort ist irreführend. Einige Monaden haben überhaupt keinen "Wrapper", ein solcher funktioniert ab einem festen Wert.
1
@ DanMandel-Monaden sind Entwurfsmuster, die einen eigenen Datentyp-Wrapper bereitstellen. Monaden sind so konzipiert, dass sie den Code des Boilerplates abstrahieren. Wenn Sie also eine Monade in Ihrem Code aufrufen, werden hinter den Kulissen Dinge erledigt, über die Sie sich keine Sorgen machen möchten. Denken Sie an Nullable <T> oder IEnumerable <T>. Was tun sie hinter den Kulissen? Das ist Monade.
sksallaj
168

Aber du hättest Monaden erfinden können!

Sigfpe sagt:

Aber all dies führt Monaden als etwas Esoterisches ein, das einer Erklärung bedarf. Aber ich möchte argumentieren, dass sie überhaupt nicht esoterisch sind. Angesichts verschiedener Probleme bei der funktionalen Programmierung wären Sie unaufhaltsam zu bestimmten Lösungen geführt worden, die alle Beispiele für Monaden sind. Tatsächlich hoffe ich, Sie dazu zu bringen, sie jetzt zu erfinden, wenn Sie es noch nicht getan haben. Es ist dann ein kleiner Schritt zu bemerken, dass alle diese Lösungen tatsächlich die gleiche getarnte Lösung sind. Und nachdem Sie dies gelesen haben, sind Sie möglicherweise besser in der Lage, andere Dokumente zu Monaden zu verstehen, da Sie alles, was Sie sehen, als etwas erkennen, das Sie bereits erfunden haben.

Viele der Probleme, die Monaden zu lösen versuchen, hängen mit dem Problem der Nebenwirkungen zusammen. Also fangen wir mit ihnen an. (Beachten Sie, dass Sie mit Monaden mehr als nur mit Nebenwirkungen umgehen können. Insbesondere viele Arten von Containerobjekten können als Monaden angesehen werden. Bei einigen Einführungen in Monaden ist es schwierig, diese beiden unterschiedlichen Verwendungen von Monaden miteinander in Einklang zu bringen und sich auf nur eine oder mehrere zu konzentrieren das andere.)

In einer imperativen Programmiersprache wie C ++ verhalten sich Funktionen nicht wie die Funktionen der Mathematik. Angenommen, wir haben eine C ++ - Funktion, die ein einzelnes Gleitkommaargument verwendet und ein Gleitkommaergebnis zurückgibt. Oberflächlich betrachtet mag es ein wenig wie eine mathematische Funktion erscheinen, die Reals Reals zuordnet, aber eine C ++ - Funktion kann mehr als nur eine Zahl zurückgeben, die von ihren Argumenten abhängt. Es kann die Werte globaler Variablen lesen und schreiben sowie Ausgaben auf den Bildschirm schreiben und Eingaben vom Benutzer empfangen. In einer reinen funktionalen Sprache kann eine Funktion jedoch nur lesen, was ihr in ihren Argumenten geliefert wird, und die einzige Möglichkeit, die Welt zu beeinflussen, sind die zurückgegebenen Werte.

Nlucaroni
quelle
9
… Am besten nicht nur im Internet, sondern überall. (Wadlers Originalarbeit Monaden für die funktionale Programmierung , die ich in meiner Antwort unten erwähnt habe, ist ebenfalls gut.) Keine der Millionen analoger Tutorials kommt dem nahe.
ShreevatsaR
13
Diese JavaScript-Übersetzung von Sigfpes Beitrag ist der neue beste Weg, um Monaden zu lernen, für Leute, die noch kein fortgeschrittenes Haskell haben!
Sam Watkins
1
So habe ich gelernt, was eine Monade ist. Den Leser durch den Prozess der Erfindung eines Konzepts zu führen, ist oft der beste Weg, das Konzept zu lehren.
Jordanien
Eine Funktion, die das Bildschirmobjekt als Argument akzeptiert und seine Kopie mit geändertem Text zurückgibt, wäre jedoch rein.
Dmitri Zaitsev
87

Eine Monade ist ein Datentyp, der zwei Operationen hat: >>=(aka bind) und return(aka unit). returnnimmt einen beliebigen Wert und erstellt damit eine Instanz der Monade. >>=Nimmt eine Instanz der Monade und ordnet eine Funktion darüber zu. (Sie können bereits sehen, dass eine Monade ein seltsamer Datentyp ist, da Sie in den meisten Programmiersprachen keine Funktion schreiben konnten, die einen beliebigen Wert annimmt und daraus einen Typ erstellt. Monaden verwenden eine Art parametrischen Polymorphismus .)

In der Haskell-Notation wird die Monadenschnittstelle geschrieben

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

Diese Operationen sollen bestimmten "Gesetzen" gehorchen, aber das ist nicht besonders wichtig: Die "Gesetze" kodifizieren nur das Verhalten vernünftiger Implementierungen der Operationen (im Grunde genommen das >>=und returnsollten sich darüber einig sein, wie Werte in Monadeninstanzen umgewandelt werden und Das>>= ist assoziativ).

Bei Monaden geht es nicht nur um Zustand und E / A: Sie abstrahieren ein gemeinsames Berechnungsmuster, das die Arbeit mit Zustand, E / A, Ausnahmen und Nichtdeterminismus umfasst. Die wahrscheinlich am einfachsten zu verstehenden Monaden sind Listen und Optionstypen:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

wo []und :sind die Listenkonstruktoren, ++ist der Verkettungsoperator und Justund Nothingsind dieMaybe Konstruktoren. Beide Monaden kapseln gemeinsame und nützliche Berechnungsmuster für ihre jeweiligen Datentypen (beachten Sie, dass beides nichts mit Nebenwirkungen oder E / A zu tun hat).

Sie müssen wirklich herumspielen, um einen nicht trivialen Haskell-Code zu schreiben, um zu verstehen, worum es bei Monaden geht und warum sie nützlich sind.

Chris Conway
quelle
Was genau meinst du mit "ordnet eine Funktion darüber zu"?
Casebash
Casebash, ich bin in der Einleitung bewusst informell. Sehen Sie sich die Beispiele am Ende an, um einen Eindruck davon zu bekommen, was "Zuordnen einer Funktion" bedeutet.
Chris Conway
3
Monade ist kein Datentyp. Es ist eine Regel für das Erstellen
Dmitri Zaitsev
@DmitriZaitsev ist richtig, Monaden stellen tatsächlich ihren eigenen Datendatentyp
bereit
78

Sie sollten zuerst verstehen, was ein Funktor ist. Verstehe vorher Funktionen höherer Ordnung.

Eine Funktion höherer Ordnung ist einfach eine Funktion, die eine Funktion als Argument verwendet.

Ein Funktor ist eine beliebige Typkonstruktion, Tfür die es eine Funktion höherer Ordnung mapgibt, die eine Funktion vom Typ a -> b(bei zwei beliebigen Typen aund b) in eine Funktion umwandelt T a -> T b. Diese mapFunktion muss auch den Gesetzen der Identität und Zusammensetzung entsprechen, so dass die folgenden Ausdrücke für alle pund q(Haskell-Notation) wahr sind :

map id = id
map (p . q) = map p . map q

Ein aufgerufener ListTypkonstruktor ist beispielsweise ein Funktor, wenn er mit einer Funktion vom Typ ausgestattet ist, (a -> b) -> List a -> List bdie den oben genannten Gesetzen entspricht. Die einzige praktische Umsetzung liegt auf der Hand. Die resultierende List a -> List bFunktion durchläuft die angegebene Liste, ruft die (a -> b)Funktion für jedes Element auf und gibt die Liste der Ergebnisse zurück.

Ein monadisch ist im wesentlichen nur ein Funktors Tmit zwei zusätzlichen Methoden join, vom Typ T (T a) -> T aund unit(manchmal genannt return, forkoder pure) vom Typ a -> T a. Für Listen in Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Warum ist das nützlich? Weil Sie zum Beispiel mapüber eine Liste mit einer Funktion gehen könnten, die eine Liste zurückgibt. JoinNimmt die resultierende Liste der Listen und verkettet sie. Listist eine Monade, weil dies möglich ist.

Sie können eine Funktion schreiben , die tut map, dann join. Diese Funktion heißt bindoder flatMapoder (>>=)oder oder (=<<). So wird normalerweise eine Monadeninstanz in Haskell angegeben.

Eine Monade muss bestimmte Gesetze erfüllen, nämlich joinassoziative. Dies bedeutet, wenn Sie einen Wert xvom Typ haben, [[[a]]]dannjoin (join x) gleich sein sollte join (map join x). Und puremuss eine Identität für sein join, dass join (pure x) == x.

Apocalisp
quelle
3
geringfügige Ergänzung zu def der Funktion 'höherer Ordnung': Sie können OR RETURN-Funktionen übernehmen. Deshalb sind sie "höher", weil sie Dinge mit sich selbst machen.
Kevin gewann am
9
Nach dieser Definition ist die Addition eine Funktion höherer Ordnung. Es nimmt eine Zahl und gibt eine Funktion zurück, die diese Zahl einer anderen hinzufügt. Nein, Funktionen höherer Ordnung sind also ausschließlich Funktionen, deren Domäne aus Funktionen besteht.
Apocalisp
Das Video ' Brian Beckman: Fürchte dich nicht vor der Monade ' folgt dieser Logik.
icc97
48

[Haftungsausschluss: Ich versuche immer noch, Monaden vollständig zu groken. Das Folgende ist genau das, was ich bisher verstanden habe. Wenn es falsch ist, ruft mich hoffentlich jemand, der sich auskennt, auf dem Teppich an.]

Arnar schrieb:

Monaden sind einfach eine Möglichkeit, Dinge zu verpacken und Methoden bereitzustellen, um Operationen an den verpackten Sachen durchzuführen, ohne sie auszupacken.

Genau das ist es. Die Idee geht so:

  1. Sie nehmen einen Wert und verpacken ihn mit zusätzlichen Informationen. So wie der Wert von einer bestimmten Art ist (z. B. eine Ganzzahl oder eine Zeichenfolge), so sind die zusätzlichen Informationen von einer bestimmten Art.

    Zum Beispiel könnte diese zusätzliche Information eine Maybeoder eine sein IO.

  2. Dann haben Sie einige Operatoren, mit denen Sie die umschlossenen Daten bearbeiten können, während Sie diese zusätzlichen Informationen mitnehmen. Diese Operatoren verwenden die zusätzlichen Informationen, um zu entscheiden, wie das Verhalten der Operation für den umschlossenen Wert geändert werden soll.

    ZB Maybe Intkann a ein Just Intoder sein Nothing. Wenn Sie nun a Maybe Intzu a hinzufügen Maybe Int, prüft der Operator, ob beide Just Ints enthalten sind, und wenn ja, packt er das Ints aus, übergibt ihm den Additionsoperator und verpackt das Ergebnis erneut Intin ein neues Just Int(was gültig ist) Maybe Int) und geben damit a zurück Maybe Int. Wenn jedoch einer von ihnen ein NothingInside war, kehrt dieser Operator sofort zurück Nothing, was wiederum gültig ist Maybe Int. Auf diese Weise können Sie so tun, als wären Ihre Maybe Ints nur normale Zahlen, und sie regelmäßig berechnen. Wenn Sie eine erhalten Nothing, werden Ihre Gleichungen immer noch das richtige Ergebnis liefern - ohne dass Sie Nothingüberall Wurfprüfungen durchführen müssen .

Aber das Beispiel ist genau das, wofür es passiert Maybe. Wenn die zusätzlichen Informationen eine wären, würde stattdessen der IOfür IOs definierte spezielle Operator aufgerufen, und er könnte vor dem Hinzufügen etwas völlig anderes tun. (OK, zwei IO Ints zusammen zu addieren ist wahrscheinlich unsinnig - ich bin mir noch nicht sicher.) (Auch wenn Sie auf das geachtet habenMaybe Beispiel beachtet haben, haben Sie auch festgestellt, dass das „Umschließen eines Werts mit zusätzlichen Dingen“ nicht immer korrekt ist. Aber es ist schwierig um genau, richtig und präzise zu sein, ohne unergründlich zu sein.)

Grundsätzlich bedeutet "Monade" ungefähr "Muster" . Anstelle eines Buches mit informell erklärten und speziell benannten Mustern haben Sie jetzt ein Sprachkonstrukt - Syntax und alles -, mit dem Sie neue Muster als Dinge in Ihrem Programm deklarieren können . (Die Ungenauigkeit hier ist, dass alle Muster einer bestimmten Form folgen müssen, daher ist eine Monade nicht ganz so allgemein wie ein Muster. Aber ich denke, das ist der engste Begriff, den die meisten Menschen kennen und verstehen.)

Und deshalb finden die Leute Monaden so verwirrend: weil sie so ein generisches Konzept sind. Zu fragen, was etwas zu einer Monade macht, ist ähnlich vage wie zu fragen, was etwas zu einem Muster macht.

Denken Sie jedoch an die Auswirkungen einer syntaktischen Unterstützung in der Sprache für die Idee eines Musters: Anstatt das Buch der Viererbande lesen und sich die Konstruktion eines bestimmten Musters merken zu müssen, schreiben Sie einfach Code, der dieses Muster in einem Agnostiker implementiert. generischer Weg einmal und dann sind Sie fertig! Sie können dieses Muster dann wiederverwenden, z. B. Besucher oder Strategie oder Fassade oder was auch immer, indem Sie die Vorgänge in Ihrem Code damit dekorieren, ohne es immer wieder neu implementieren zu müssen!

Deshalb finden Leute, die Monaden verstehen , sie so nützlich : Es ist kein Elfenbeinturm-Konzept, auf das intellektuelle Snobs stolz sind (OK, das natürlich auch, Teehee), aber es macht den Code tatsächlich einfacher.

Aristoteles Pagaltzis
quelle
12
Manchmal ist eine Erklärung eines "Lernenden" (wie Sie) für einen anderen Lernenden relevanter als eine Erklärung eines Experten. Die Lernenden denken gleich :)
Adrian
Was etwas zu einer Monade macht, ist die Existenz einer Funktion mit Typ M (M a) -> M a. Die Tatsache, dass Sie daraus einen Typ M a -> (a -> M b) -> M bmachen können, macht sie nützlich.
Jeremy List
"Monade" bedeutet ungefähr "Muster" ... nein.
Vielen Dank, dass Sie
44

Nach langem Bemühen denke ich, dass ich die Monade endlich verstehe. Nachdem ich meine lange Kritik an der überwiegend am besten bewerteten Antwort noch einmal gelesen habe, werde ich diese Erklärung anbieten.

Es gibt drei Fragen, die beantwortet werden müssen, um Monaden zu verstehen:

  1. Warum brauchst du eine Monade?
  2. Was ist eine Monade?
  3. Wie wird eine Monade implementiert?

Wie ich in meinen ursprünglichen Kommentaren festgestellt habe, sind zu viele Monadenerklärungen in Frage 3 enthalten, ohne und bevor Frage 2 oder Frage 1 wirklich angemessen behandelt wird.

Warum brauchst du eine Monade?

Reine Funktionssprachen wie Haskell unterscheiden sich von imperativen Sprachen wie C oder Java darin, dass ein reines Funktionsprogramm nicht unbedingt Schritt für Schritt in einer bestimmten Reihenfolge ausgeführt wird. Ein Haskell-Programm ähnelt eher einer mathematischen Funktion, bei der Sie die "Gleichung" in beliebig vielen möglichen Reihenfolgen lösen können. Dies bringt eine Reihe von Vorteilen mit sich, unter anderem, dass bestimmte Arten von Fehlern ausgeschlossen werden, insbesondere solche, die sich auf Dinge wie "Zustand" beziehen.

Es gibt jedoch bestimmte Probleme, die mit diesem Programmierstil nicht so einfach zu lösen sind. Einige Dinge, wie die Konsolenprogrammierung und die Datei-E / A, müssen in einer bestimmten Reihenfolge ausgeführt werden oder den Status beibehalten. Eine Möglichkeit, dieses Problem zu lösen, besteht darin, eine Art Objekt zu erstellen, das den Status einer Berechnung darstellt, sowie eine Reihe von Funktionen, die ein Statusobjekt als Eingabe verwenden und ein neues modifiziertes Statusobjekt zurückgeben.

Erstellen wir also einen hypothetischen "Status" -Wert, der den Status eines Konsolenbildschirms darstellt. Es ist nicht wichtig, wie genau dieser Wert aufgebaut ist. Angenommen, es handelt sich um ein Array von ASCII-Zeichen mit Bytelänge, das das darstellt, was derzeit auf dem Bildschirm angezeigt wird, und ein Array, das die letzte vom Benutzer eingegebene Eingabezeile im Pseudocode darstellt. Wir haben einige Funktionen definiert, die den Konsolenstatus annehmen, ändern und einen neuen Konsolenstatus zurückgeben.

consolestate MyConsole = new consolestate;

Um die Konsolenprogrammierung durchzuführen, aber auf rein funktionale Weise, müssten Sie viele Funktionsaufrufe ineinander verschachteln.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

Wenn Sie auf diese Weise programmieren, bleibt der "reine" Funktionsstil erhalten, während Änderungen an der Konsole in einer bestimmten Reihenfolge erzwungen werden. Aber wir werden wahrscheinlich mehr als nur ein paar Operationen gleichzeitig ausführen wollen, wie im obigen Beispiel. Verschachtelungsfunktionen auf diese Weise werden unansehnlich. Was wir wollen, ist Code, der im Wesentlichen dasselbe wie oben tut, aber ein bisschen mehr so ​​geschrieben ist:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

Dies wäre in der Tat eine bequemere Art, es zu schreiben. Wie machen wir das aber?

Was ist eine Monade?

Sobald Sie einen Typ (z. B. consolestate) haben, den Sie zusammen mit einer Reihe von Funktionen definieren, die speziell für diesen Typ entwickelt wurden, können Sie das gesamte Paket dieser Dinge in eine "Monade" verwandeln, indem Sie einen Operator wie :(bind) definieren, der dies automatisch tut speist Rückgabewerte links in Funktionsparameter rechts ein und alift Operator, der normale Funktionen in Funktionen umwandelt, die mit dieser bestimmten Art von Bindungsoperator arbeiten.

Wie wird eine Monade implementiert?

Sehen Sie andere Antworten, die ziemlich frei zu sein scheinen, um in die Details davon zu springen.

Breton
quelle
Sequenzierung ist nicht der einzige Grund, eine Monade zu definieren. Eine Monade ist einfach jeder Funktor, der gebunden und zurückgekehrt ist. Binden und zurückgeben geben Ihnen Sequenzierung. Aber sie geben auch andere Dinge. Beachten Sie auch, dass Ihre bevorzugte imperative Sprache effektiv eine ausgefallene E / A-Monade mit OO-Klassen ist. Wenn Sie Monaden einfach definieren möchten, können Sie das Interpreter-Muster einfach verwenden. Definieren Sie eine DSL als Monade und interpretieren Sie sie!
Nomen
Hier ist eine Implementierung: github.com/brianspinos777/Programming_cheat_sheets/blob/master/…
Brian Joseph Spinos
38

Nachdem ich vor einigen Jahren eine Antwort auf diese Frage gegeben habe, glaube ich, dass ich diese Antwort verbessern und vereinfachen kann mit ...

Eine Monade ist eine Technik zur Funktionskomposition, die die Behandlung einiger Eingabeszenarien mithilfe einer Kompositionsfunktion externalisiert. bind , um Eingaben während der Komposition vorzuverarbeiten.

In der normalen Komposition wird die Funktion compose (>>)verwendet, um die zusammengesetzte Funktion nacheinander auf das Ergebnis ihres Vorgängers anzuwenden. Wichtig ist, dass die zusammengesetzte Funktion alle Szenarien ihrer Eingabe verarbeiten muss.

(x -> y) >> (y -> z)

Dieses Design kann verbessert werden, indem die Eingabe so umstrukturiert wird, dass relevante Zustände leichter abgefragt werden können. So kann yder Wert statt einfach nur so werden, Mbwie zum Beispiel, (is_OK, b)wenn er yeinen Gültigkeitsbegriff enthält.

Wenn die Eingabe beispielsweise möglicherweise nur eine Zahl ist, können Sie den Typ so umstrukturieren, booldass er das Vorhandensein einer gültigen Zahl und einer Zahl im Tupel anzeigt, z bool * float. Die zusammengesetzten Funktionen müssten nun keine Eingabezeichenfolge mehr analysieren, um festzustellen, ob eine Zahl vorhanden ist, sondern könnten lediglich den boolTeil eines Tupels untersuchen.

(Ma -> Mb) >> (Mb -> Mc)

Auch hier erfolgt die Komposition auf natürliche Weise mit, composesodass jede Funktion alle Szenarien ihrer Eingabe einzeln behandeln muss, obwohl dies jetzt viel einfacher ist.

Was wäre jedoch, wenn wir den Aufwand für die Befragung in Zeiten auslagern könnten, in denen die Behandlung eines Szenarios Routine ist? Was ist zum Beispiel, wenn unser Programm nichts tut, wenn die Eingabe nicht in Ordnung ist, wie in wann is_OK?false . Wenn dies getan würde, müssten zusammengesetzte Funktionen dieses Szenario nicht selbst behandeln, was ihren Code dramatisch vereinfacht und eine weitere Ebene der Wiederverwendung bewirkt.

Um diese Externalisierung zu erreichen, könnten wir eine Funktion verwenden, bind (>>=)um das compositionanstelle von auszuführen compose. Anstatt einfach Werte von der Ausgabe einer Funktion auf die Eingabe einer anderen zu übertragen, Bindwürde dies den MTeil von untersuchen Maund entscheiden, ob und wie die zusammengesetzte Funktion auf die angewendet werden soll a. Natürlich bindwürde die Funktion speziell für unsere spezielle definiert M, um ihre Struktur überprüfen und jede Art von Anwendung ausführen zu können, die wir wollen. Nichtsdestotrotz akann das alles sein, da es bindlediglich den durchläufta Uninspektionierte an die zusammengesetzte Funktion übergeben wird, wenn es die erforderliche Anwendung bestimmt. Außerdem müssen sich die zusammengesetzten Funktionen selbst nicht mehr mit dem befassenMTeil der Eingabestruktur entweder, um sie zu vereinfachen. Daher...

(a -> Mb) >>= (b -> Mc) oder prägnanter Mb >>= (b -> Mc)

Kurz gesagt, eine Monade externalisiert und liefert dadurch ein Standardverhalten bei der Behandlung bestimmter Eingabeszenarien, sobald die Eingabe so ausgelegt ist, dass sie ausreichend verfügbar ist. Dieses Design ist ein shell and contentModell, bei dem die Shell Daten enthält, die für die Anwendung der zusammengesetzten Funktion relevant sind, von der Funktion abgefragt wird und nur für diese verfügbar bleibt bind.

Daher ist eine Monade drei Dinge:

  1. eine MHülle für monadenrelevante Informationen,
  2. eine bindFunktion, die implementiert ist, um diese Shell-Informationen bei der Anwendung der zusammengesetzten Funktionen auf die in der Shell gefundenen Inhaltswerte zu verwenden, und
  3. zusammensetzbare Funktionen des Formulars, a -> Mbdie Ergebnisse liefern, die monadische Verwaltungsdaten enthalten.

Im Allgemeinen ist die Eingabe in eine Funktion weitaus restriktiver als ihre Ausgabe, die beispielsweise Fehlerbedingungen enthalten kann. Daher ist die MbErgebnisstruktur im Allgemeinen sehr nützlich. Beispielsweise gibt der Divisionsoperator keine Zahl zurück, wenn der Divisor ist 0.

Zusätzlich kann monads Wrap-Funktionen enthalten, die Werte ain den monadischen Typ Maund allgemeine Funktionen a -> bin monadische Funktionen einschließen a -> Mb, indem ihre Ergebnisse nach der Anwendung umbrochen werden . Natürlich bindsind solche Wrap-Funktionen spezifisch für M. Ein Beispiel:

let return a = [a]
let lift f a = return (f a)

Das Design der bindFunktion setzt unveränderliche Datenstrukturen und reine Funktionen voraus, andere Dinge werden komplex und Garantien können nicht gegeben werden. Als solche gibt es monadische Gesetze:

Gegeben...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Dann...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitybedeutet, dass binddie Reihenfolge der Bewertung unabhängig vom Zeitpunkt der bindAnwendung erhalten bleibt. Das heißt, in der Associativityobigen Definition erzwingt die frühzeitige Bewertung der Klammern bindingvon fund gführt nur zu einer Funktion, die erwartet, Maum die zu vervollständigen bind. Daher muss die Bewertung von Mabestimmt werden, bevor ihr Wert angewendet werden kann fund das Ergebnis wiederum angewendet wird g.

George
quelle
"... aber ich hoffe andere finden es nützlich" es war in der Tat nützlich für mich, trotz aller hervorgehobenen Sätze: D
Dies ist die prägnanteste und klarste Erklärung für Monaden, die ich je gelesen / gesehen / gehört habe. Vielen Dank!
James
Es gibt einen wichtigen Unterschied zwischen Monade und Monoid. Monade ist eine Regel zum "Zusammenstellen" von Funktionen zwischen verschiedenen Typen, daher bilden sie keine binäre Operation, wie sie für Monoide erforderlich ist. Weitere Informationen finden Sie hier: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev
Ja. Du hast Recht. Dein Artikel war über meinem Kopf :). Ich fand diese Behandlung jedoch sehr hilfreich (und fügte sie meiner als Anweisung für andere hinzu). Vielen Dank für die Heads-Ups: stackoverflow.com/a/7829607/1612190
George
2
Möglicherweise haben Sie die algebraische Gruppentheorie mit der Kategorietheorie verwechselt , aus der Monad stammt. Ersteres ist die Theorie der algebraischen Gruppen, die nichts miteinander zu tun hat.
Dmitri Zaitsev
37

Eine Monade ist effektiv eine Form von "Typoperator". Es wird drei Dinge tun. Zuerst wird ein Wert eines Typs in einen anderen Typ "umbrochen" (oder auf andere Weise konvertiert) (normalerweise als "monadischer Typ" bezeichnet). Zweitens werden alle Operationen (oder Funktionen) des zugrunde liegenden Typs für den monadischen Typ verfügbar gemacht. Schließlich wird es Unterstützung für die Kombination seines Selbst mit einer anderen Monade bieten, um eine zusammengesetzte Monade zu erzeugen.

Die "Vielleicht-Monade" entspricht im Wesentlichen den "nullbaren Typen" in Visual Basic / C #. Es nimmt einen nicht nullbaren Typ "T" und konvertiert ihn in einen "nullbaren <T>" und definiert dann, was alle binären Operatoren auf einem nullbaren <T> bedeuten.

Nebenwirkungen werden ähnlich dargestellt. Es wird eine Struktur erstellt, die neben dem Rückgabewert einer Funktion auch Beschreibungen von Nebenwirkungen enthält. Die "aufgehobenen" Operationen kopieren dann Nebenwirkungen, wenn Werte zwischen Funktionen übergeben werden.

Sie werden aus mehreren Gründen eher als "Monaden" als als der leicht verständliche Name "Typoperatoren" bezeichnet:

  1. Monaden haben Einschränkungen, was sie tun können (siehe Definition für Details).
  2. Diese Einschränkungen entsprechen zusammen mit der Tatsache, dass es sich um drei Operationen handelt, der Struktur einer sogenannten Monade in der Kategorietheorie, die ein dunkler Zweig der Mathematik ist.
  3. Sie wurden von Befürwortern "reiner" funktionaler Sprachen entworfen
  4. Befürworter reiner funktionaler Sprachen wie obskure Zweige der Mathematik
  5. Da die Mathematik unklar ist und Monaden mit bestimmten Programmierstilen verbunden sind, verwenden die Leute das Wort Monade eher als eine Art geheimen Händedruck. Aus diesem Grund hat sich niemand die Mühe gemacht, in einen besseren Namen zu investieren.
Scott Wisniewski
quelle
1
Monaden wurden nicht "entworfen", sondern von einer Domäne (Kategorietheorie) auf eine andere angewendet (E / A in rein funktionalen Programmiersprachen). Hat Newton den Kalkül 'entworfen'?
Jared Updike
1
Die obigen Punkte 1 und 2 sind richtig und nützlich. Die Punkte 4 und 5 sind eine Art Ad-Hominem, auch wenn sie mehr oder weniger zutreffen. Sie helfen nicht wirklich, Monaden zu erklären.
Jared Updike
13
Re: 4, 5: Das "geheime Händedruck" -Ding ist ein roter Hering. Die Programmierung ist voller Fachsprache. Haskell nennt Dinge einfach so, wie sie sind, ohne vorzugeben, etwas wiederzuentdecken. Wenn es in der Mathematik bereits existiert, warum einen neuen Namen dafür erfinden? Der Name ist wirklich nicht der Grund, warum Menschen keine Monaden bekommen; Sie sind ein subtiles Konzept. Die durchschnittliche Person versteht wahrscheinlich Addition und Multiplikation. Warum bekommt sie nicht das Konzept einer abelschen Gruppe? Weil es abstrakter und allgemeiner ist und diese Person nicht die Arbeit getan hat, um sich mit dem Konzept zu beschäftigen. Eine Namensänderung würde nicht helfen.
Jared Updike
16
Seufz ... Ich greife Haskell nicht an ... Ich habe einen Witz gemacht. Ich verstehe es also nicht wirklich, "ad hominem" zu sein. Ja, der Kalkül wurde "entworfen". Das ist der Grund, warum zum Beispiel Kalkülschülern die Leibniz-Notation beigebracht wird und nicht das eklige Zeug, das Netwton verwendet. Besseres Design. Gute Namen helfen viel zu verstehen. Wenn ich Abelian Groups "Distended Wrinkle Pods" nenne, haben Sie möglicherweise Probleme, mich zu verstehen. Sie könnten sagen "aber dieser Name ist Unsinn", niemand würde sie jemals so nennen. Für Leute, die noch nie von Kategorietheorie gehört haben, klingt "Monade" nach Unsinn.
Scott Wisniewski
4
@ Scott: Entschuldigung, wenn meine ausführlichen Kommentare den Anschein erweckten, als würde ich mich gegen Haskell wehren. Ich genieße Ihren Humor über den geheimen Händedruck und Sie werden feststellen, dass ich sagte, dass er mehr oder weniger wahr ist. :-) Wenn Sie Abelian Groups "Distended Wrinkle Pods" nennen würden, würden Sie den gleichen Fehler machen, wenn Sie versuchen, Monaden einen "besseren Namen" zu geben (vgl. F # "Berechnungsausdrücke"): Der Begriff existiert und Menschen, die sich darum kümmern, wissen, welche Monaden sind, aber nicht was "warme Fuzzy-Dinge" sind (oder "Berechnungsausdrücke"). Wenn ich Ihre Verwendung des Begriffs "Typoperator" richtig verstehe, gibt es viele andere Typoperatoren als Monaden.
Jared Updike
35

(Siehe auch die Antworten unter Was ist eine Monade? )

Eine gute Motivation für Monaden ist Sigfpe (Dan Piponi), Sie hätten Monaden erfinden können ! (Und vielleicht haben Sie schon) . Es gibt eine Menge anderer Monaden-Tutorials , von denen viele fälschlicherweise versuchen, Monaden mit verschiedenen Analogien in "einfachen Worten" zu erklären: Dies ist der Irrtum der Monaden-Tutorials ; vermeide sie.

Wie DR MacIver in Sagen Sie uns, warum Ihre Sprache scheiße ist :

Also, Dinge, die ich an Haskell hasse:

Beginnen wir mit dem Offensichtlichen. Monaden-Tutorials. Nein, keine Monaden. Speziell die Tutorials. Sie sind endlos, übertrieben und lieber Gott, sie sind langweilig. Außerdem habe ich nie überzeugende Beweise dafür gesehen, dass sie tatsächlich helfen. Lesen Sie die Klassendefinition, schreiben Sie Code und überwinden Sie den beängstigenden Namen.

Sie sagen, Sie verstehen die Vielleicht-Monade? Gut, du bist auf dem Weg. Fangen Sie einfach an, andere Monaden zu verwenden, und früher oder später werden Sie verstehen, was Monaden im Allgemeinen sind.

[Wenn Sie mathematisch orientiert sind, möchten Sie möglicherweise Dutzende von Tutorials ignorieren und die Definition lernen oder Vorlesungen in Kategorietheorie folgen :) Der Hauptteil der Definition besteht darin, dass eine Monade M einen "Typkonstruktor" enthält, der für jeden definiert vorhandener Typ "T" ein neuer Typ "MT" und einige Möglichkeiten zum Hin- und Herwechseln zwischen "regulären" Typen und "M" -Typen.]

Überraschenderweise ist eine der besten Einführungen in Monaden tatsächlich eine der frühen wissenschaftlichen Arbeiten, in denen Monaden vorgestellt werden, Philip Wadlers Monaden für die funktionale Programmierung . Im Gegensatz zu vielen künstlichen Tutorials gibt es tatsächlich praktische, nicht triviale Motivationsbeispiele.

ShreevatsaR
quelle
2
Das einzige Problem mit Wadlers Papier ist, dass die Notation anders ist, aber ich stimme zu, dass das Papier ziemlich überzeugend und eine klare, prägnante Motivation für die Anwendung von Monaden ist.
Jared Updike
+1 für den "Monaden-Tutorial-Irrtum". Tutorials zu Monaden ähneln mehreren Tutorials, in denen versucht wird, das Konzept der Ganzzahl zu erklären. Ein Tutorial würde sagen: "1 ist ähnlich wie ein Apfel"; Ein anderes Tutorial sagt: "2 ist wie eine Birne"; Ein dritter sagt: "3 ist im Grunde eine Orange". Aber Sie bekommen nie das ganze Bild von einem einzelnen Tutorial. Was ich daraus gezogen habe, ist, dass Monaden ein abstraktes Konzept sind, das für viele ganz unterschiedliche Zwecke verwendet werden kann.
stakx - nicht mehr beitragen
@stakx: Ja, stimmt. Aber ich habe nicht gemeint, dass Monaden eine Abstraktion sind, die man nicht lernen kann oder sollte; nur, dass es am besten ist, es zu lernen, nachdem Sie genug konkrete Beispiele gesehen haben, um eine einzelne zugrunde liegende Abstraktion wahrzunehmen. Siehe meine andere Antwort hier .
ShreevatsaR
5
Manchmal habe ich das Gefühl, dass es so viele Tutorials gibt, die versuchen, den Leser davon zu überzeugen, dass Monaden nützlich sind, indem sie Code verwenden, der komplizierte oder nützliche Dinge tut. Das hat mein Verständnis monatelang behindert. Ich lerne nicht so. Ich bevorzuge es, extrem einfachen Code zu sehen, etwas Dummes zu tun, das ich mental durchmachen kann, und ich konnte ein solches Beispiel nicht finden. Ich kann nicht lernen, ob das erste Beispiel eine Monade ist, um eine komplizierte Grammatik zu analysieren. Ich kann lernen, ob es eine Monade ist, ganze Zahlen zu summieren.
Rafael S. Calsaverini
Die Erwähnung nur des Typkonstruktors
Dmitri Zaitsev
23

Monaden sollen den Fluss steuern, welche abstrakten Datentypen zu Daten gehören.

Mit anderen Worten, viele Entwickler sind mit der Idee von Sets, Listen, Wörterbüchern (oder Hashes oder Maps) und Bäumen vertraut. Innerhalb dieser Datentypen gibt es viele Sonderfälle (z. B. InsertionOrderPreservingIdentityHashMap).

Wenn viele Entwickler jedoch mit dem Programm "Flow" konfrontiert werden, sind sie nicht viel mehr Konstrukten ausgesetzt als wenn, switch / case, do, while, goto (grr) und (vielleicht) Closures.

Eine Monade ist also einfach ein Kontrollflusskonstrukt. Ein besserer Ausdruck, um die Monade zu ersetzen, wäre "Kontrolltyp".

Daher verfügt eine Monade über Steckplätze für Steuerlogik, Anweisungen oder Funktionen. In Datenstrukturen bedeutet dies, dass Sie in einigen Datenstrukturen Daten hinzufügen und entfernen können.

Zum Beispiel die "wenn" -Monade:

if( clause ) then block

Im einfachsten Fall gibt es zwei Slots - eine Klausel und einen Block. Die ifMonade wird normalerweise erstellt, um das Ergebnis der Klausel auszuwerten, und wenn nicht falsch, den Block auszuwerten. Viele Entwickler werden nicht in Monaden eingeführt, wenn sie "Wenn" lernen, und es ist einfach nicht notwendig, Monaden zu verstehen, um effektive Logik zu schreiben.

Monaden können komplizierter werden, genauso wie Datenstrukturen komplizierter werden können, aber es gibt viele breite Kategorien von Monaden, die eine ähnliche Semantik haben können, aber unterschiedliche Implementierungen und Syntax.

Natürlich können auf die gleiche Weise, wie Datenstrukturen über Monaden iteriert oder durchlaufen werden, Monaden ausgewertet werden.

Compiler unterstützen möglicherweise benutzerdefinierte Monaden oder nicht. Haskell sicherlich. Ioke hat einige ähnliche Fähigkeiten, obwohl der Begriff Monade in der Sprache nicht verwendet wird.

Nick Drew
quelle
14

Mein Lieblings-Monaden-Tutorial:

http://www.haskell.org/haskellwiki/All_About_Monads

(von 170.000 Treffern bei einer Google-Suche nach "Monad Tutorial"!)

@Stu: Der Sinn von Monaden besteht darin, dass Sie (normalerweise) sequentielle Semantik zu ansonsten reinem Code hinzufügen können. Sie können sogar Monaden erstellen (mithilfe von Monadentransformatoren) und eine interessantere und kompliziertere kombinierte Semantik erhalten, z. B. das Parsen mit Fehlerbehandlung, gemeinsam genutztem Status und Protokollierung. All dies ist in reinem Code möglich. Monaden ermöglichen es Ihnen lediglich, ihn zu abstrahieren und in modularen Bibliotheken wiederzuverwenden (immer gut in der Programmierung). Außerdem bieten Sie eine praktische Syntax, damit er zwingend erscheint.

Haskell hat bereits eine Operatorüberladung [1]: Es verwendet Typklassen ähnlich wie Schnittstellen in Java oder C #, aber Haskell lässt zufällig auch nicht-alphanumerische Token wie + && und> als Infix-IDs zu. Es ist nur eine Überladung des Operators in Ihrer Sichtweise, wenn Sie "Überladen des Semikolons" meinen [2]. Es klingt wie schwarze Magie und bittet um Mühe, "das Semikolon zu überladen" (Bild unternehmungslustige Perl-Hacker, die Wind von dieser Idee bekommen), aber der Punkt ist, dass es ohne Monaden kein Semikolon gibt, da rein funktionaler Code keine explizite Sequenzierung erfordert oder erlaubt.

Das klingt alles viel komplizierter als nötig. sigfpes Artikel ist ziemlich cool, verwendet aber Haskell, um ihn zu erklären, was das Henne-Ei-Problem des Verstehens von Haskell, um Monaden zu groken, und des Verstehens von Monaden, um Haskell zu groken, nicht löst.

[1] Dies ist ein von Monaden getrenntes Problem, aber Monaden verwenden die Operatorüberladungsfunktion von Haskell.

[2] Dies ist auch eine übermäßige Vereinfachung, da der Operator für die Verkettung monadischer Aktionen >> = (ausgesprochen "bind") ist, aber es gibt syntaktischen Zucker ("do"), mit dem Sie geschweifte Klammern und Semikolons und / oder Einrückungen und Zeilenumbrüche verwenden können.

Jared Updike
quelle
9

Ich habe in letzter Zeit anders über Monaden nachgedacht. Ich habe sie als mathematische Abstraktion der Ausführungsreihenfolge angesehen , die neue Arten von Polymorphismus ermöglicht.

Wenn Sie eine imperative Sprache verwenden und einige Ausdrücke der Reihe nach schreiben, wird der Code IMMER genau in dieser Reihenfolge ausgeführt.

Und im einfachen Fall, wenn Sie eine Monade verwenden, fühlt es sich genauso an - Sie definieren eine Liste von Ausdrücken, die in der richtigen Reihenfolge vorkommen. Je nachdem, welche Monade Sie verwenden, wird Ihr Code möglicherweise in der richtigen Reihenfolge (wie in der E / A-Monade) parallel über mehrere Elemente gleichzeitig (wie in der Listenmonade) ausgeführt und möglicherweise auf halbem Weg angehalten (wie in der Vielleicht-Monade). Es kann eine Pause einlegen, um später wieder aufgenommen zu werden (wie in einer Wiederaufnahme-Monade), es kann zurückgespult werden und von vorne beginnen (wie in einer Transaktions-Monade) oder es kann teilweise zurückgespult werden, um andere Optionen auszuprobieren (wie in einer Logik-Monade). .

Und da Monaden polymorph sind, können Sie je nach Bedarf denselben Code in verschiedenen Monaden ausführen.

In einigen Fällen ist es außerdem möglich, Monaden (mit Monadentransformatoren) miteinander zu kombinieren, um mehrere Funktionen gleichzeitig zu erhalten.

jes5199
quelle
9

Ich bin noch neu in Monaden, aber ich dachte, ich würde einen Link teilen, den ich wirklich gut zum Lesen fand (MIT BILDERN !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ Monaden für den Laien / (keine Zugehörigkeit)

Grundsätzlich war das warme und unscharfe Konzept, das ich aus dem Artikel erhalten habe, das Konzept, dass Monaden im Grunde genommen Adapter sind, die es ermöglichen, dass unterschiedliche Funktionen auf komponierbare Weise funktionieren, dh mehrere Funktionen aneinanderreihen und mischen und anpassen können, ohne sich um eine inkonsistente Rückkehr sorgen zu müssen Typen und so. Die BIND-Funktion ist also dafür verantwortlich, Äpfel mit Äpfeln und Orangen mit Orangen zu halten, wenn wir versuchen, diese Adapter herzustellen. Und die LIFT-Funktion ist dafür verantwortlich, "untergeordnete" Funktionen zu übernehmen und sie zu "aktualisieren", um mit BIND-Funktionen zu arbeiten und auch zusammensetzbar zu sein.

Ich hoffe, ich habe es richtig verstanden und vor allem hoffe ich, dass der Artikel eine gültige Sicht auf Monaden hat. Wenn nichts anderes, hat dieser Artikel meinen Appetit geweckt, mehr über Monaden zu lernen.

Magicpanda
quelle
Die Python-Beispiele machten es leicht zu verstehen! Danke für das Teilen.
Ryan Efendy
8

Zusätzlich zu den hervorragenden Antworten oben möchte ich Ihnen einen Link zum folgenden Artikel (von Patrick Thomson) anbieten, in dem Monaden erläutert werden, indem das Konzept auf die JavaScript-Bibliothek jQuery bezogen wird (und wie "Methodenverkettung" zur Manipulation des DOM verwendet wird). : jQuery ist eine Monade

Die jQuery-Dokumentation selbst bezieht sich nicht auf den Begriff "Monade", sondern spricht über das wahrscheinlich bekanntere "Builder-Muster". Dies ändert nichts an der Tatsache, dass Sie dort eine richtige Monade haben, ohne es zu merken.

Siggboy
quelle
Wenn Sie jQuery verwenden, kann diese Erklärung sehr hilfreich sein, insbesondere wenn Ihr Haskell nicht stark ist
Byteclub
10
JQuery ist nachdrücklich keine Monade. Der verlinkte Artikel ist falsch.
Tony Morris
1
"Nachdrücklich" zu sein ist nicht sehr überzeugend. Eine nützliche Diskussion zu diesem Thema finden Sie unter Ist jQuery eine Monade
Stapelüberlauf
1
Siehe auch Douglas Crackfords Google Talk Monads and Gonads und seinen Javascript-Code zum Ausführen von Modads, um das ähnliche Verhalten von AJAX-Bibliotheken und Versprechungen zu erweitern: Douglascrockford / Monad · GitHub
Nealmcb
7

Eine Monade ist eine Möglichkeit, Berechnungen miteinander zu kombinieren, die einen gemeinsamen Kontext haben. Es ist wie ein Rohrnetz. Beim Aufbau des Netzwerks fließen keine Daten durch das Netzwerk. Aber wenn ich alle Bits mit 'bind' und 'return' zusammengesetzt habe, rufe ich so etwas auf runMyMonad monad dataund die Daten fließen durch die Pipes.

Alex
quelle
1
Das ist eher anwendbar als Monade. Bei Monaden müssen Sie Daten aus den Pipes abrufen, bevor Sie die nächste zu verbindende Pipe auswählen können.
Peaker
Ja, Sie beschreiben den Antragsteller, nicht die Monade. Monad baut das nächste Rohrsegment an Ort und Stelle, abhängig von den Daten, die diesen Punkt erreicht haben, innerhalb des Rohrs.
Will Ness
6

In der Praxis ist monad eine benutzerdefinierte Implementierung eines Funktionszusammensetzungsoperators, der Nebenwirkungen und inkompatible Eingabe- und Rückgabewerte (für die Verkettung) berücksichtigt.

Mateusz Charytoniuk
quelle
5

Wenn ich richtig verstanden habe, wird IEnumerable von Monaden abgeleitet. Ich frage mich, ob das ein interessanter Ansatz für diejenigen von uns aus der C # -Welt sein könnte.

Für das, was es wert ist, hier sind einige Links zu Tutorials, die mir geholfen haben (und nein, ich habe immer noch nicht verstanden, was Monaden sind).

Benjol
quelle
5

Die beiden Dinge, die mir am besten geholfen haben, als ich davon erfuhr, waren:

Kapitel 8, "Functional Parsers", aus Graham Huttons Buch Programming in Haskell . Hier werden Monaden eigentlich gar nicht erwähnt, aber wenn Sie Kapitel durcharbeiten und wirklich alles darin verstehen können, insbesondere wie eine Folge von Bindungsoperationen bewertet wird, werden Sie die Interna von Monaden verstehen. Erwarten Sie, dass dies mehrere Versuche dauert.

Das Tutorial Alles über Monaden . Dies gibt einige gute Beispiele für ihre Verwendung, und ich muss sagen, dass die Analogie in Appendex, die ich für mich gearbeitet habe.

cjs
quelle
5

Monoid scheint etwas zu sein, das sicherstellt, dass alle Operationen, die für ein Monoid und einen unterstützten Typ definiert sind, immer einen unterstützten Typ innerhalb des Monoid zurückgeben. ZB Beliebige Zahl + Beliebige Zahl = Eine Zahl, keine Fehler.

Während die Division zwei Brüche akzeptiert und einen Bruch zurückgibt, der die Division durch Null als Unendlichkeit in haskell irgendwo definiert (was zufällig ein Bruch ist) ...

In jedem Fall scheinen Monaden nur eine Möglichkeit zu sein, um sicherzustellen, dass sich Ihre Operationskette vorhersehbar verhält, und eine Funktion, die behauptet, Num -> Num zu sein, die mit einer anderen Funktion von Num-> Num zusammengesetzt ist, die mit x aufgerufen wird, nicht Sagen Sie, schießen Sie die Raketen.

Wenn wir andererseits eine Funktion haben, die die Raketen abfeuert, können wir sie mit anderen Funktionen zusammensetzen, die auch die Raketen abfeuern, da unsere Absicht klar ist - wir wollen die Raketen abfeuern -, aber es wird nicht versucht Drucken von "Hello World" aus irgendeinem Grund.

In Haskell ist main vom Typ IO () oder IO [()], die Unterscheidung ist seltsam und ich werde nicht darauf eingehen, aber ich denke, dass Folgendes passiert:

Wenn ich main habe, möchte ich, dass es eine Kette von Aktionen ausführt. Der Grund, warum ich das Programm ausführe, ist, einen Effekt zu erzeugen - normalerweise über IO. Auf diese Weise kann ich E / A-Vorgänge hauptsächlich miteinander verketten, um E / A-Vorgänge auszuführen, sonst nichts.

Wenn ich versuche, etwas zu tun, das keine "E / A zurückgibt", beschwert sich das Programm, dass die Kette nicht fließt, oder im Grunde "Wie hängt dies mit dem zusammen, was wir versuchen - eine E / A-Aktion", scheint es zu erzwingen der Programmierer, um seinen Gedankengang beizubehalten, ohne abzuweichen und über das Abfeuern der Raketen nachzudenken, während er Algorithmen zum Sortieren erstellt - die nicht fließen.

Grundsätzlich scheinen Monaden ein Tipp für den Compiler zu sein: "Hey, Sie kennen diese Funktion, die hier eine Zahl zurückgibt. Sie funktioniert nicht immer, sie kann manchmal eine Zahl erzeugen und manchmal gar nichts. Behalten Sie dies einfach bei." Verstand". Wenn Sie dies wissen und versuchen, eine monadische Aktion zu behaupten, kann die monadische Aktion als Ausnahme für die Kompilierungszeit fungieren und sagen: "Hey, das ist eigentlich keine Zahl, das kann eine Zahl sein, aber Sie können nicht davon ausgehen, dass Sie etwas tun." um sicherzustellen, dass der Durchfluss akzeptabel ist. " Dies verhindert ein unvorhersehbares Programmverhalten - zu einem angemessenen Teil.

Es scheint, dass es bei Monaden nicht um Reinheit oder Kontrolle geht, sondern darum, die Identität einer Kategorie aufrechtzuerhalten, für die alles Verhalten vorhersehbar und definiert ist oder nicht kompiliert wird. Sie können nichts tun, wenn von Ihnen erwartet wird, dass Sie etwas tun, und Sie können nichts tun, wenn von Ihnen erwartet wird, dass Sie nichts tun (sichtbar).

Der größte Grund, an den ich für Monaden denken könnte, ist - schauen Sie sich den Prozedur- / OOP-Code an, und Sie werden feststellen, dass Sie nicht wissen, wo das Programm beginnt oder endet. Sie sehen nur viel Springen und viel Mathe , Magie und Raketen. Sie werden es nicht pflegen können, und wenn Sie können, werden Sie viel Zeit damit verbringen, sich mit dem gesamten Programm zu beschäftigen, bevor Sie einen Teil davon verstehen können, da die Modularität in diesem Kontext auf voneinander abhängigen "Abschnitten" basiert. von Code, wobei Code so optimiert ist, dass er so verwandt wie möglich ist, um Effizienz / Wechselbeziehung zu versprechen. Monaden sind sehr konkret und per Definition gut definiert und stellen sicher, dass der Programmfluss analysiert und Teile isoliert werden können, die schwer zu analysieren sind - da sie selbst Monaden sind. Eine Monade scheint eine " oder das Universum zerstören oder sogar die Zeit verzerren - wir haben weder eine Ahnung noch eine Garantie dafür, dass ES DAS IST, WAS ES IST. Eine Monade GARANTIERT, DASS ES IST, WAS ES IST. Das ist sehr mächtig. oder das Universum zerstören oder sogar die Zeit verzerren - wir haben weder eine Ahnung noch eine Garantie dafür, dass ES DAS IST, WAS ES IST. Eine Monade GARANTIERT, DASS ES IST, WAS ES IST. Das ist sehr mächtig.

Alle Dinge in der "realen Welt" scheinen Monaden zu sein, in dem Sinne, dass sie an bestimmte beobachtbare Gesetze gebunden sind, die Verwirrung verhindern. Dies bedeutet nicht, dass wir alle Operationen dieses Objekts nachahmen müssen, um Klassen zu erstellen. Stattdessen können wir einfach sagen: "Ein Quadrat ist ein Quadrat", nichts als ein Quadrat, nicht einmal ein Rechteck oder ein Kreis, und "ein Quadrat hat Fläche von der Länge einer der vorhandenen Dimensionen multipliziert mit sich selbst. Egal welches Quadrat Sie haben, wenn es ein Quadrat im 2D-Raum ist, kann seine Fläche absolut nichts anderes sein als seine quadratische Länge, es ist fast trivial zu beweisen. Dies ist sehr mächtig, weil Wir müssen keine Aussagen machen, um sicherzustellen, dass unsere Welt so ist, wie sie ist. Wir nutzen lediglich die Auswirkungen der Realität, um zu verhindern, dass unsere Programme aus der Bahn geraten.

Ich bin so ziemlich garantiert falsch, aber ich denke, das könnte jemandem da draußen helfen, also hoffentlich hilft es jemandem.

Dmitry
quelle
5

Im Zusammenhang mit Scala finden Sie Folgendes als einfachste Definition. Grundsätzlich ist flatMap (oder bind) 'assoziativ' und es gibt eine Identität.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

Z.B

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

ANMERKUNG Genau genommen ist die Definition einer Monade in der funktionalen Programmierung nicht dieselbe wie die Definition einer Monade in der Kategorietheorie , die abwechselnd von mapund definiert wird flatten. Obwohl sie unter bestimmten Zuordnungen eine Art Äquivalent sind. Diese Präsentation ist sehr gut: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

samthebest
quelle
5

Diese Antwort beginnt mit einem motivierenden Beispiel, arbeitet das Beispiel durch, leitet ein Beispiel für eine Monade ab und definiert formal "Monade".

Betrachten Sie diese drei Funktionen im Pseudocode:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

fnimmt ein bestelltes Paar des Formulars <x, messages>und gibt ein bestelltes Paar zurück. Der erste Artikel bleibt unberührt und wird "called f. "an den zweiten Artikel angehängt . Gleiches gilt für g.

Sie können diese Funktionen zusammenstellen und Ihren ursprünglichen Wert zusammen mit einer Zeichenfolge erhalten, die angibt, in welcher Reihenfolge die Funktionen aufgerufen wurden:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Sie mögen die Tatsache nicht fund gsind dafür verantwortlich, ihre eigenen Protokollnachrichten an die vorherigen Protokollierungsinformationen anzuhängen. (Man stelle sich vor für die Zwecke der Beweisführung, dass anstelle Strings anhängt, fund gmüssen komplizierte Logik auf dem zweiten Element des Paares ausführen Es wäre ein Schmerz zu wiederholen, dass komplizierte Logik in zwei -. Oder mehr -. Verschiedene Funktionen)

Sie bevorzugen es, einfachere Funktionen zu schreiben:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Aber schauen Sie sich an, was passiert, wenn Sie sie komponieren:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

Das Problem ist, dass das Übergeben eines Paares an eine Funktion Ihnen nicht das gibt, was Sie wollen. Aber was wäre , wenn Sie füttern ein Paar in eine Funktion:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Lesen Sie feed(f, m)als "Feed min f". Um zu füttern ein Paar <x, messages>in eine Funktion fzu übergeben x , in f, erhalten <y, message>aus fund kehren <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Beachten Sie, was passiert, wenn Sie drei Dinge mit Ihren Funktionen tun:

Erstens: Wenn Sie einen Wert wickeln und dann füttern das resultierende Paar in eine Funktion:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

Dies entspricht der Übergabe des Werts an die Funktion.

Zweitens: Wenn Sie ein Paar füttern in wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Das ändert nichts an dem Paar.

Drittens: Wenn Sie eine Funktion definieren , die nehmen xund führen g(x)in f:

h(x) := feed(f, g(x))

und füttere ein Paar hinein:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

Dies entspricht dem Einspeisen des Paares gund dem Einspeisen des resultierenden Paares f.

Sie haben fast eine Monade. Jetzt müssen Sie nur noch die Datentypen in Ihrem Programm kennen.

Was für ein Wert ist das <x, "called f. ">? Nun, das hängt davon ab, welche Art von Wert xist. Wenn xes sich um einen Typ handelt t, ist Ihr Paar ein Wert vom Typ "Paar tund Zeichenfolge". Nennen Sie diesen Typ M t.

Mist ein Typkonstruktor: Mallein bezieht sich nicht auf einen Typ, sondern M _auf einen Typ, sobald Sie die Lücke mit einem Typ ausfüllen. An M intist ein Paar aus einem int und einem String. An M stringist ein Paar aus einer Zeichenfolge und einer Zeichenfolge. Usw.

Herzlichen Glückwunsch, Sie haben eine Monade erstellt!

Formal ist Ihre Monade das Tupel <M, feed, wrap>.

Eine Monade ist ein Tupel, <M, feed, wrap>in dem:

  • M ist ein Typkonstruktor.
  • feednimmt eine (Funktion, die a nimmt tund eine zurückgibt M u) und eine M tund gibt eine zurück M u.
  • wrapnimmt ein vund gibt ein zurück M v.

t,, uund vsind drei beliebige Typen, die gleich sein können oder nicht. Eine Monade erfüllt die drei Eigenschaften, die Sie für Ihre spezifische Monade bewiesen haben:

  • Das Einspeisen eines Wraps tin eine Funktion entspricht dem Übergeben des Unwrapped tin die Funktion.

    Formal: feed(f, wrap(x)) = f(x)

  • Einspeisen M tin wraptut nichts mit dem M t.

    Formal: feed(wrap, m) = m

  • Einspeisen M t(nennen m) in eine Funktion, die

    • geht ting
    • bekommt ein M u(nenne es n) vong
    • füttert ninf

    ist das gleiche wie

    • Einspeisung ming
    • bekommen nvong
    • Einspeisung ninf

    Formal: feed(h, m) = feed(f, feed(g, m))woh(x) := feed(f, g(x))

In der Regel feedheißt bind(AKA >>=in Haskell) und wrapheißt return.

Jordanien
quelle
5

Ich werde versuchen, Monadim Zusammenhang mit Haskell zu erklären .

Bei der funktionalen Programmierung ist die Funktionszusammensetzung wichtig. Dadurch kann unser Programm aus kleinen, leicht lesbaren Funktionen bestehen.

Nehmen wir an, wir haben zwei Funktionen: g :: Int -> Stringund f :: String -> Bool.

Wir können tun (f . g) x, was genau das gleiche ist wie f (g x), wo xein IntWert ist.

Bei der Komposition / Anwendung des Ergebnisses einer Funktion auf eine andere ist es wichtig, dass die Typen übereinstimmen. Im obigen Fall muss der Typ des zurückgegebenen Ergebnisses gmit dem Typ übereinstimmen, der von akzeptiert wird f.

Aber manchmal befinden sich Werte in Kontexten, und dies macht es etwas weniger einfach, Typen auszurichten. (Es ist sehr nützlich, Werte in Kontexten zu haben. Beispielsweise stellt der Maybe IntTyp einen IntWert dar, der möglicherweise nicht vorhanden ist. Der IO StringTyp stellt einen StringWert dar, der aufgrund einiger Nebenwirkungen vorhanden ist.)

Nehmen wir an, wir haben jetzt g1 :: Int -> Maybe Stringund f1 :: String -> Maybe Bool. g1und f1sind sehr ähnlich gund fsind.

Wir können nicht (f1 . g1) xoder f1 (g1 x)wo xist ein IntWert. Der Typ des zurückgegebenen Ergebnisses entspricht g1nicht den f1Erwartungen.

Wir könnten komponieren fund gmit dem .Operator, aber jetzt können wir nicht komponieren f1und g1mit .. Das Problem ist, dass wir einen Wert in einem Kontext nicht einfach an eine Funktion übergeben können, die einen Wert erwartet, der sich nicht in einem Kontext befindet.

Wäre es nicht schön, wenn wir einen Operator zum Komponieren g1und so vorstellen f1, dass wir schreiben können (f1 OPERATOR g1) x? g1Gibt einen Wert in einem Kontext zurück. Der Wert wird aus dem Kontext genommen und auf angewendet f1. Und ja, wir haben einen solchen Operator. Es ist <=<.

Wir haben auch den >>=Operator, der genau dasselbe für uns tut, allerdings in einer etwas anderen Syntax.

Wir schreiben : g1 x >>= f1. g1 xist ein Maybe IntWert. Der >>=Operator hilft dabei, diesen IntWert aus dem Kontext "Vielleicht nicht da" zu entfernen und auf ihn anzuwenden f1. Das Ergebnis von f1, das a ist Maybe Bool, ist das Ergebnis der gesamten >>=Operation.

Und schließlich, warum ist Monadnützlich? Denn Monaddie Typklasse, die den >>=Operator definiert , entspricht weitgehend der EqTypklasse, die die Operatoren ==und definiert /=.

Zusammenfassend Monaddefiniert die Typklasse den >>=Operator, mit dem wir Werte in einem Kontext (wir nennen diese monadischen Werte) an Funktionen übergeben können, die keine Werte in einem Kontext erwarten. Der Kontext wird gepflegt.

Wenn es hier eine Sache gibt, an die man sich erinnern sollte, ist es, dass man Monaddie Funktionszusammensetzung zulässt, die Werte in Kontexten beinhaltet .

Jonas
quelle
Hier ist eine Implementierung: github.com/brianspinos777/Programming_cheat_sheets/blob/master/…
Brian Joseph Spinos
IOW, Monad ist ein verallgemeinertes Funktionsaufrufprotokoll.
Will Ness
Ihre Antwort ist meiner Meinung nach am hilfreichsten. Obwohl ich sagen muss, dass ich denke, dass der Schwerpunkt auf der Tatsache liegen muss, dass die Funktionen, auf die Sie sich beziehen, nicht nur Werte in Kontexten beinhalten, sondern Werte aktiv in Kontexte setzen. So würde beispielsweise eine Funktion, f :: ma -> mb, sehr leicht mit einer anderen Funktion, g :: mb -> m c, zusammengesetzt. Aber Monaden (spezifisch binden) ermöglichen es uns, ständig Funktionen zu komponieren, die ihre Eingabe in denselben Kontext stellen, ohne dass wir zuerst den Wert aus diesem Kontext herausnehmen müssen (was effektiv Informationen aus dem Wert entfernen würde)
James
@ James Ich denke, das sollte der Schwerpunkt für Funktoren sein?
Jonas
@ Jonas Ich glaube, ich habe es nicht richtig erklärt. Wenn ich sage, dass die Funktionen Werte in Kontexte setzen, meine ich, dass sie den Typ (a -> mb) haben. Diese sind sehr nützlich, da das Einfügen eines Werts in einen Kontext neue Informationen hinzufügt, es jedoch normalerweise schwierig ist, a (a -> mb) und a (b -> mc) miteinander zu verketten, da wir den Wert nicht einfach herausnehmen können des Kontextes. Wir müssten also einen komplizierten Prozess verwenden, um diese Funktionen je nach Kontext auf sinnvolle Weise miteinander zu verketten, und Monaden ermöglichen es uns, dies unabhängig vom Kontext auf konsistente Weise zu tun.
James
5

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Prolog

Der Anwendungsoperator $von Funktionen

forall a b. a -> b

ist kanonisch definiert

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

in Bezug auf die Anwendung der Haskell-primitiven Funktion f x( infixl 10).

Die Zusammensetzung .wird definiert $als

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

und erfüllt die Äquivalenzen forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.ist assoziativ und idist seine rechte und linke Identität.

Das Kleisli Triple

Bei der Programmierung ist eine Monade ein Konstruktor vom Typ Funktor mit einer Instanz der Klasse vom Typ Monade. Es gibt mehrere äquivalente Varianten der Definition und Implementierung, die jeweils leicht unterschiedliche Intuitionen über die Monadenabstraktion enthalten.

Ein Funktor ist ein fTypkonstruktor * -> *mit einer Instanz der Funktortypklasse.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

Zusätzlich zum Befolgen des statisch erzwungenen Typprotokolls müssen Instanzen der Funktortypklasse den algebraischen Funktorgesetzen entsprechen forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Functor Berechnungen haben den Typ

forall f t. Functor f => f t

Eine Berechnung c rbesteht aus Ergebnissen r im Kontext c .

Unäre monadische Funktionen oder Kleisli-Pfeile haben den Typ

forall m a b. Functor m => a -> m b

Kleisi-Pfeile sind Funktionen, die ein Argument annehmen aund eine monadische Berechnung zurückgeben m b.

Monaden werden kanonisch im Sinne des Kleisli-Tripels definiert forall m. Functor m =>

(m, return, (=<<))

implementiert als Typklasse

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

Die Kleisli-Identität return ist ein Kleisli-Pfeil, der einen Wert tin einen monadischen Kontext fördert m. Die Erweiterung oder Kleisli-Anwendung =<< wendet einen Kleisli-Pfeil a -> m bauf die Ergebnisse einer Berechnung an m a.

Die Kleisli-Komposition <=< wird in Bezug auf die Erweiterung definiert als

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< setzt zwei Kleisli-Pfeile zusammen und wendet den linken Pfeil auf die Ergebnisse der Anwendung des rechten Pfeils an.

Instanzen der Monadentypklasse müssen den Monadengesetzen entsprechen , die in Bezug auf die Kleisli-Zusammensetzung am elegantesten angegeben sind:forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<ist assoziativ und returnist seine rechte und linke Identität.

Identität

Der Identitätstyp

type Id t = t

ist die Identitätsfunktion für Typen

Id :: * -> *

Als Funktor interpretiert,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

Im kanonischen Haskell wird die Identitätsmonade definiert

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Möglichkeit

Ein Optionstyp

data Maybe t = Nothing | Just t

codiert Berechnungen Maybe t, die nicht unbedingt zu einem Ergebnis führen t, Berechnungen, die möglicherweise „fehlschlagen“. Die Option Monade ist definiert

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe bwird nur dann auf ein Ergebnis angewendet, wenn Maybe aein Ergebnis erhalten wird.

newtype Nat = Nat Int

Die natürlichen Zahlen können als ganze Zahlen größer oder gleich Null codiert werden.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Die natürlichen Zahlen werden bei Subtraktion nicht geschlossen.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

Die Option Monade deckt eine Grundform der Ausnahmebehandlung ab.

(-? 20) <=< toNat :: Int -> Maybe Nat

Liste

Die Listenmonade über dem Listentyp

data [] t = [] | t : [t]

infixr 5 :

und seine additive Monoidoperation "anhängen"

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

codiert nichtlineare Berechnungen, [t]die eine natürliche Menge 0, 1, ...an Ergebnissen liefern t.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

Die Erweiterung =<<verkettet ++alle Listen, [b]die sich aus der Anwendung f xeines Kleisli-Pfeils ergeben, a -> [b]zu Elementen [a]einer einzigen Ergebnisliste [b].

Die richtigen Teiler einer positiven ganzen Zahl nseien

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

dann

forall n.  let { f = f <=< divisors } in f n   =   []

Bei der Definition der Monadentypklasse verwendet =<<der Haskell-Standard anstelle der Erweiterung seinen Flip, den Bind- Operator >>=.

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

In dieser Erklärung wird der Einfachheit halber die Typklassenhierarchie verwendet

class              Functor f
class Functor m => Monad m

In Haskell ist die aktuelle Standardhierarchie

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

denn nicht nur jede Monade ist ein Funktor, sondern jede Anwendung ist eine Funktion und jede Monade ist auch eine Anwendung.

Unter Verwendung der Listenmonade wird der imperative Pseudocode verwendet

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

grob übersetzt in den do-Block ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

das äquivalente Monadenverständnis ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

und der Ausdruck

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

Notation und Monadenverständnis sind syntaktischer Zucker für verschachtelte Bindungsausdrücke. Der Bindungsoperator wird für die lokale Namensbindung von monadischen Ergebnissen verwendet.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

wo

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

Die Schutzfunktion ist definiert

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

wo der Einheitentyp oder "leeres Tupel"

data () = ()

Additive Monaden , die Auswahl und Misserfolg unterstützen, können mithilfe einer Typklasse abstrahiert werden

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

wo failund <|>bilden ein Monoidforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

und failist das absorbierende / vernichtende Nullelement von additiven Monaden

_ =<< fail  =  fail

Wenn in

guard (even p) >> return p

even pist wahr, dann erzeugt der Wächter [()]und nach der Definition >>der lokalen konstanten Funktion

\ _ -> return p

wird auf das Ergebnis angewendet (). Wenn false, erzeugt der Wächter die Liste monad's fail( []), die kein Ergebnis für die Anwendung eines Kleisli-Pfeils ergibt >>, sodass dieser pübersprungen wird.

Zustand

Monaden werden bekanntlich verwendet, um zustandsbehaftete Berechnungen zu codieren.

Ein Zustandsprozessor ist eine Funktion

forall st t. st -> (t, st)

das übergeht einen Zustand stund ergibt ein Ergebnis t. Der Staat st kann alles sein. Nichts, Flagge, Anzahl, Array, Handle, Maschine, Welt.

Der Typ der Statusprozessoren wird normalerweise aufgerufen

type State st t = st -> (t, st)

Die State Processor Monad ist der freundliche * -> *Funktor State st. Kleisli-Pfeile der State-Processor-Monade sind Funktionen

forall st a b. a -> (State st) b

Im kanonischen Haskell ist die Lazy-Version der State Processor-Monade definiert

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Ein Statusprozessor wird ausgeführt, indem ein Anfangszustand bereitgestellt wird:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

Der staatliche Zugang wird durch Primitive getund putAbstraktionsmethoden über staatsmässige Monaden ermöglicht:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stdeklariert eine funktionale Abhängigkeit des Zustandstyps stvon der Monade m; dass a State tzum Beispiel den Zustandstyp als teindeutig bestimmt.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

mit dem Einheitentyp analog zu voidin C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets wird häufig mit Datensatzfeld-Accessoren verwendet.

Das Zustandsmonadenäquivalent des variablen Threadings

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

wo s0 :: Intist das gleichermaßen referenziell transparent, aber unendlich eleganter und praktischer

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)ist eine Berechnung des Typs State Int (), mit Ausnahme des Effekts, der äquivalent zu ist return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

Das Monadengesetz der Assoziativität kann in Bezug auf geschrieben werden >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

oder

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

Wie bei der ausdrucksorientierten Programmierung (z. B. Rust) repräsentiert die letzte Anweisung eines Blocks seine Ausbeute. Der Bindungsoperator wird manchmal als "programmierbares Semikolon" bezeichnet.

Iterationskontrollstrukturprimitive aus der strukturierten imperativen Programmierung werden monadisch emuliert

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Input-Output

data World

Die Monade des I / O-Weltzustandsprozessors ist eine Versöhnung von reinem Haskell und der realen Welt, von funktionaler denotativer und zwingender operativer Semantik. Ein enges Analogon zur tatsächlichen strengen Umsetzung:

type IO t = World -> (t, World)

Die Interaktion wird durch unreine Grundelemente erleichtert

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

Die Verunreinigung von Code, der IOGrundelemente verwendet, wird vom Typsystem permanent protokolliert. Weil Reinheit fantastisch ist IO, bleibt das , was passiert , drin IO.

unsafePerformIO :: IO t -> t

Oder sollte es zumindest.

Die Typensignatur eines Haskell-Programms

main :: IO ()
main = putStrLn "Hello, World!"

erweitert sich auf

World -> ((), World)

Eine Funktion, die eine Welt verändert.

Epilog

Die Kategorie, deren Objekte Haskell-Typen sind und deren Morphismen Funktionen zwischen Haskell-Typen sind, ist die Kategorie „schnell und locker“ Hask.

Ein Funktor Tist eine Zuordnung von einer Kategorie Czu einer Kategorie D. für jedes Objekt in Ceinem Objekt inD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

und für jeden Morphismus in Ceinem Morphismus inD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

wo X, Ysind Objekte in C. HomC(X, Y)ist die Homomorphismusklasse aller Morphismen X -> Yin C. Der Funktor muss die Identität und Zusammensetzung des Morphismus bewahren, die „Struktur“ von C, in D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

Die Kleisli-Kategorie einer Kategorie Cwird durch ein Kleisli-Tripel angegeben

<T, eta, _*>

eines Endofunktors

T : C -> C

( f), ein Identitätsmorphismus eta( return) und ein Erweiterungsoperator *( =<<).

Jeder Kleisli-Morphismus in Hask

      f :  X -> T(Y)
      f :: a -> m b

vom Nebenstellenbetreiber

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

erhält einen Morphismus in Haskder Kategorie Kleisli

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Die Zusammensetzung in der Kategorie Kleisli .Twird in Bezug auf die Erweiterung angegeben

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

und erfüllt die Kategorie Axiome

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

welche unter Anwendung der Äquivalenztransformationen

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

in Bezug auf die Erweiterung sind kanonisch gegeben

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Monaden können auch nicht als Kleislsche Erweiterung, sondern als natürliche Transformation muin der Programmierung bezeichnet werden join. Eine Monade wird muals Triple über einer Kategorie Ceines Endofunktors definiert

     T :  C -> C
     f :: * -> *

und zwei natürliche Transformationen

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

Befriedigung der Äquivalenzen

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

Die Monadentypklasse wird dann definiert

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

Die kanonische muImplementierung der Option Monade:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

Die concatFunktion

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

ist die joinder Liste Monade.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Implementierungen von joinkönnen mithilfe der Äquivalenz aus dem Erweiterungsformular übersetzt werden

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

Die umgekehrte Übersetzung von muzum Erweiterungsformular ist gegeben durch

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Aber warum sollte eine so abstrakte Theorie für die Programmierung von Nutzen sein?

Die Antwort ist einfach: Als Informatiker legen wir Wert auf Abstraktion ! Wenn wir die Schnittstelle zu einer Softwarekomponente entwerfen, möchten wir, dass sie so wenig wie möglich über die Implementierung aussagt. Wir möchten in der Lage sein, die Implementierung durch viele Alternativen zu ersetzen, viele andere "Instanzen" desselben "Konzepts". Wenn wir eine generische Schnittstelle für viele Programmbibliotheken entwerfen, ist es noch wichtiger, dass die von uns ausgewählte Schnittstelle eine Vielzahl von Implementierungen aufweist. Es ist die Allgemeinheit des Monadenkonzepts, die wir so hoch schätzen, weil die Kategorietheorie so abstrakt ist, dass ihre Konzepte für die Programmierung so nützlich sind.

Es ist daher nicht verwunderlich, dass die Verallgemeinerung der Monaden, die wir unten präsentieren, auch einen engen Zusammenhang mit der Kategorietheorie hat. Wir betonen jedoch, dass unser Zweck sehr praktisch ist: Es geht nicht darum, die Kategorietheorie zu implementieren, sondern einen allgemeineren Weg zu finden, um Kombinatorbibliotheken zu strukturieren. Es ist einfach unser Glück, dass Mathematiker bereits einen Großteil der Arbeit für uns geleistet haben!

von der Verallgemeinerung von Monaden zu Pfeilen von John Hughes

Benutzer6428287
quelle
4

Was die Welt braucht, ist ein weiterer Monaden-Blog-Beitrag, aber ich denke, dies ist nützlich, um vorhandene Monaden in freier Wildbahn zu identifizieren.

Sierpinski-Dreieck

Das Obige ist ein Fraktal namens Sierpinski-Dreieck, das einzige Fraktal, an das ich mich erinnern kann, es gezeichnet zu haben. Fraktale sind selbstähnliche Strukturen wie das obige Dreieck, bei denen die Teile dem Ganzen ähnlich sind (in diesem Fall genau die Hälfte der Skala als übergeordnetes Dreieck).

Monaden sind Fraktale. Bei einer monadischen Datenstruktur können ihre Werte zusammengesetzt werden, um einen anderen Wert der Datenstruktur zu bilden. Aus diesem Grund ist es für die Programmierung nützlich, und aus diesem Grund tritt es in vielen Situationen auf.

Eugene Yokota
quelle
3
Meinst du "was die Welt nicht braucht ..."? Schöne Analogie!
Groverboy
@ icc97 du hast recht - die bedeutung ist klar genug. Sarkasmus unbeabsichtigt, Entschuldigung an den Autor.
Groverboy
Was die Welt braucht, ist ein weiterer Kommentarthread, der einen Sarkasmus bestätigt, aber wenn ich ihn sorgfältig lese, habe ich ihn geschrieben, aber das sollte es klar machen.
Eugene Yokota
4

Lassen Sie das folgende " {| a |m}" einige monadische Daten darstellen. Ein Datentyp, der Folgendes ankündigt a:

        (I got an a!)
          /        
    {| a |m}

Funktion f,, weiß, wie man eine Monade erstellt, wenn sie nur eine a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Hier sehen wir Funktion, fversucht eine Monade zu bewerten, wird aber zurechtgewiesen.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funktion ffindet einen Weg, das amit zu extrahieren >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Wenig fweiß, die Monade und >>=sind in Absprache.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

Aber worüber reden sie eigentlich? Nun, das hängt von der Monade ab. Nur abstrakt zu sprechen hat nur begrenzten Nutzen. Sie müssen einige Erfahrungen mit bestimmten Monaden haben, um das Verständnis zu konkretisieren.

Zum Beispiel der Datentyp Vielleicht

 data Maybe a = Nothing | Just a

hat eine Monadeninstanz, die sich wie folgt verhält ...

Worin, wenn der Fall ist Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Aber für den Fall von Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Die Vielleicht-Monade lässt eine Berechnung also fortgesetzt werden, wenn sie tatsächlich die von aihr angekündigten enthält , bricht die Berechnung jedoch ab, wenn dies nicht der Fall ist. Das Ergebnis ist jedoch immer noch ein Stück monadischer Daten, jedoch nicht die Ausgabe von f. Aus diesem Grund wird die Vielleicht-Monade verwendet, um den Kontext des Scheiterns darzustellen.

Verschiedene Monaden verhalten sich unterschiedlich. Listen sind andere Datentypen mit monadischen Instanzen. Sie verhalten sich wie folgt:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

In diesem Fall wusste die Funktion, wie eine Liste aus ihrer Eingabe erstellt wird, wusste jedoch nicht, was mit zusätzlichen Eingaben und zusätzlichen Listen zu tun ist. Die bind >>=half fdurch Kombinieren der mehrere Ausgänge aus. Ich füge dieses Beispiel hinzu, um zu zeigen, dass es zwar >>=für das Extrahieren verantwortlich ist a, aber auch Zugriff auf die eventuell gebundene Ausgabe von hat f. In der Tat wird es niemals etwas extrahieren, es asei denn, es weiß, dass die endgültige Ausgabe denselben Kontexttyp hat.

Es gibt andere Monaden, die verwendet werden, um unterschiedliche Kontexte darzustellen. Hier sind einige weitere Charakterisierungen. Die IOMonade hat eigentlich keine a, aber sie kennt einen Mann und wird das afür Sie besorgen . Die State stMonade hat einen geheimen Vorrat st, den sie funter dem Tisch weitergeben wird, obwohl sie fgerade nach einem gefragt hat a. Die Reader rMonade ähnelt State st, obwohl sie nur einen fBlick darauf werfen kann r.

Der Punkt bei all dem ist, dass jede Art von Daten, die sich selbst als Monade deklariert, einen Kontext zum Extrahieren eines Werts aus der Monade deklariert. Der große Gewinn von all dem? Nun, es ist einfach genug, eine Berechnung mit einem Kontext zu formulieren. Es kann jedoch unordentlich werden, wenn mehrere kontextreiche Berechnungen aneinandergereiht werden. Die Monadenoperationen sorgen dafür, dass die Interaktionen des Kontexts aufgelöst werden, damit der Programmierer dies nicht muss.

Beachten Sie, dass die Verwendung von die >>=ein Durcheinander erleichtert, indem ein Teil der Autonomie weggenommen wird f. Das heißt, im obigen Fall von Nothingzum Beispiel kann fnicht mehr entschieden werden, was im Fall von zu tun ist Nothing; es ist in verschlüsselt >>=. Dies ist der Kompromiss. Wenn es notwendig war fzu entscheiden, was im Fall von zu tun ist Nothing, dann fsollte eine Funktion von Maybe abis gewesen sein Maybe b. In diesem Fall Maybeist es irrelevant, eine Monade zu sein.

Beachten Sie jedoch, dass ein Datentyp manchmal seine Konstruktoren nicht exportiert (siehe E / A), und wenn wir mit dem angekündigten Wert arbeiten möchten, haben wir keine andere Wahl, als mit seiner monadischen Schnittstelle zu arbeiten.

Trevor Cook
quelle
3

Eine Monade wird verwendet, um Objekte mit sich änderndem Status zu kapseln. Es tritt am häufigsten in Sprachen auf, in denen Sie sonst keinen veränderbaren Status haben (z. B. Haskell).

Ein Beispiel wäre für Datei-E / A.

Sie können eine Monade für Datei-E / A verwenden, um die sich ändernde Statusnatur nur auf den Code zu isolieren, der die Monade verwendet hat. Der Code innerhalb der Monade kann den sich ändernden Zustand der Welt außerhalb der Monade effektiv ignorieren - dies macht es viel einfacher, über die Gesamtwirkung Ihres Programms nachzudenken.

1800 INFORMATION
quelle
3
Nach meinem Verständnis sind Monaden mehr als das. Das Einkapseln eines veränderlichen Zustands in eine "reine" funktionale Sprache ist nur eine Anwendung von Monaden.
thSoft