1694 Stimmen

Was ist eine Monade?

Nachdem ich mich kürzlich kurz mit Haskell beschäftigt habe, was wäre ein kurz, prägnant, praktisch eine Erklärung, was eine Monade im Wesentlichen ist?

Die meisten Erklärungen, auf die ich gestoßen bin, waren ziemlich unzugänglich und enthielten keine praktischen Details.

13 Stimmen

Eric Lippert schrieb eine Antwort auf diese Fragen ( stackoverflow.com/questions/2704652/ ), die aufgrund einiger Probleme auf einer separaten Seite zu finden ist.

74 Stimmen

Hier ist eine neue Einführung in die Verwendung von Javascript - ich fand sie sehr lesenswert.

7 Stimmen

1310voto

JacquesB Punkte 40790

Erstens: Der Begriff Monade ist ein wenig nichtssagend, wenn man kein Mathematiker ist. Ein alternativer Begriff ist Berechnungsprogramm die etwas besser beschreibt, wofür sie eigentlich nützlich sind.

Sie sind ein Muster für die Verkettung von Operationen. Es sieht ein wenig aus wie die Methodenverkettung in objektorientierten Sprachen, aber der Mechanismus ist etwas anders.

Das Muster wird vor allem in funktionalen Sprachen verwendet (insbesondere in Haskell, das durchgängig Monaden verwendet), kann aber in jeder Sprache verwendet werden, die Funktionen höherer Ordnung unterstützt (d. h. Funktionen, die andere Funktionen als Argumente annehmen können).

Arrays in JavaScript unterstützen das Muster, so dass wir dies als erstes Beispiel verwenden können.

Das Wesentliche des Musters ist, dass wir einen Typ ( Array in diesem Fall), die eine Methode hat, die eine Funktion als Argument nimmt. Die übergebene Operation muss eine Instanz desselben Typs zurückgeben (d.h. eine Array ).

Zunächst ein Beispiel für eine Methodenverkettung, die nicht das Monadenmuster verwenden:

[1,2,3].map(x => x + 1)

Das Ergebnis ist [2,3,4] . Der Code entspricht nicht dem Monadenmuster, da die Funktion, die wir als Argument angeben, eine Zahl und kein Array zurückgibt. Die gleiche Logik in Monadenform würde lauten:

[1,2,3].flatMap(x => [x + 1])

Hier liefern wir eine Operation, die eine Array so dass es jetzt dem Muster entspricht. Die flatMap Methode führt die angegebene Funktion für jedes Element im Array aus. Sie erwartet bei jedem Aufruf ein Array als Ergebnis (statt einzelner Werte), fasst aber die resultierende Menge von Arrays zu einem einzigen Array zusammen. Das Endergebnis ist also das gleiche, nämlich das Array [2,3,4] .

(Das Funktionsargument, das einer Methode wie map o flatMap wird in JavaScript oft als "Rückruf" bezeichnet. Ich werde es "Operation" nennen, da es allgemeiner ist).

Wenn wir mehrere Vorgänge (auf herkömmliche Weise) verketten:

[1,2,3].map(a => a + 1).filter(b => b != 3)

Ergebnisse im Array [2,4]

Die gleiche Verkettung in Monadenform:

[1,2,3].flatMap(a => [a + 1]).flatMap(b => b != 3 ? [b] : [])

Ergibt das gleiche Ergebnis, das Array [2,4] .

Sie werden sofort feststellen, dass die Monadenform um einiges hässlicher ist als die Nicht-Monadenform! Das zeigt nur, dass Monaden nicht unbedingt "gut" sind. Sie sind ein Muster, das manchmal vorteilhaft ist und manchmal nicht.

Beachten Sie, dass das Monadenmuster auf andere Weise kombiniert werden kann:

[1,2,3].flatMap(a => [a + 1].flatMap(b => b != 3 ? [b] : []))

Hier ist die Bindung verschachtelt und nicht verkettet, aber das Ergebnis ist das gleiche. Dies ist eine wichtige Eigenschaft von Monaden, wie wir später sehen werden. Sie bedeutet, dass zwei kombinierte Operationen wie eine einzige Operation behandelt werden können.

Die Operation darf ein Array mit verschiedenen Elementtypen zurückgeben, z. B. die Umwandlung eines Arrays mit Zahlen in ein Array mit Strings oder etwas anderes, solange es ein Array bleibt.

Dies lässt sich mit Hilfe der Typescript-Notation etwas formeller beschreiben. Ein Array hat den Typ Array<T> , donde T ist der Typ der Elemente im Array. Die Methode flatMap() nimmt ein Funktionsargument vom Typ T => Array<U> und gibt eine Array<U> .

Verallgemeinert ist eine Monade ein beliebiger Typ Foo<Bar> die eine "bind"-Methode hat, die ein Funktionsargument vom Typ Bar => Foo<Baz> und gibt eine Foo<Baz> .

Diese Antworten was Monaden sind. Der Rest dieser Antwort wird versuchen, anhand von Beispielen zu erklären, warum Monaden ein nützliches Muster in einer Sprache wie Haskell sein können, die eine gute Unterstützung dafür bietet.

Haskell und Do-Notation

Um das map/filter-Beispiel direkt nach Haskell zu übersetzen, ersetzen wir flatMap mit dem >>= Betreiber:

[1,2,3] >>= \a -> [a+1] >>= \b -> if b == 3 then [] else [b] 

El >>= Operator ist die Bindungsfunktion in Haskell. Sie tut dasselbe wie flatMap in JavaScript, wenn der Operand eine Liste ist, aber es ist überladen mit unterschiedlicher Bedeutung für andere Typen.

Aber Haskell hat auch eine eigene Syntax für Monadenausdrücke, die do -Block, der den Bindungsoperator vollständig ausblendet:

 do a <- [1,2,3] 
    b <- [a+1] 
    if b == 3 then [] else [b] 

Dadurch wird das "Klempnerhandwerk" ausgeblendet, und Sie können sich auf die tatsächlichen Vorgänge konzentrieren, die bei jedem Schritt angewendet werden.

In einem do -Block, jede Zeile ist eine Operation. Es gilt nach wie vor die Einschränkung, dass alle Operationen im Block den gleichen Typ zurückgeben müssen. Da der erste Ausdruck eine Liste ist, müssen die anderen Operationen ebenfalls eine Liste zurückgeben.

Der Rückenpfeil <- sieht täuschend echt wie eine Zuweisung aus, aber beachten Sie, dass dies der Parameter ist, der in der Bindung übergeben wird. Wenn also der Ausdruck auf der rechten Seite eine Liste von Ganzzahlen ist, wird die Variable auf der linken Seite eine einzelne Ganzzahl sein - aber für jede Ganzzahl in der Liste ausgeführt werden.

Beispiel: Sichere Navigation (der Typ Maybe)

Genug von Listen, sehen wir uns an, wie das Monadenmuster für andere Typen nützlich sein kann.

Einige Funktionen geben möglicherweise nicht immer einen gültigen Wert zurück. In Haskell wird dies durch das Maybe -type, das eine Option ist, die entweder Just value o Nothing .

Die Verkettung von Operationen, die immer einen gültigen Wert zurückgeben, ist natürlich einfach:

streetName = getStreetName (getAddress (getUser 17)) 

Was aber, wenn eine der Funktionen Folgendes zurückgeben könnte Nothing ? Wir müssen jedes Ergebnis einzeln prüfen und den Wert nur an die nächste Funktion weitergeben, wenn er nicht Nothing :

case getUser 17 of
      Nothing -> Nothing 
      Just user ->
         case getAddress user of
            Nothing -> Nothing 
            Just address ->
              getStreetName address

Ziemlich viele sich wiederholende Kontrollen! Stellen Sie sich vor, die Kette wäre länger. Haskell löst das Problem mit dem Monadenmuster für Maybe :

do
  user <- getUser 17
  addr <- getAddress user
  getStreetName addr

この do -Block ruft die Bindungsfunktion für die Maybe Typ (da das Ergebnis des ersten Ausdrucks ein Maybe ). Die bind-Funktion führt die folgende Operation nur aus, wenn der Wert Just value , andernfalls übergibt es einfach die Nothing entlang.

Hier wird das Monaden-Muster verwendet, um sich wiederholenden Code zu vermeiden. Dies ist vergleichbar mit der Verwendung von Makros in anderen Sprachen zur Vereinfachung der Syntax, obwohl Makros das gleiche Ziel auf ganz andere Weise erreichen.

Beachten Sie, dass es sich um die Kombination des Monadenmusters und der monadenfreundlichen Syntax in Haskell, die zu einem saubereren Code führen. In einer Sprache wie JavaScript ohne spezielle Syntaxunterstützung für Monaden würde das Monadenmuster den Code in diesem Fall wohl kaum vereinfachen können.

Veränderlicher Zustand

Haskell unterstützt keine veränderlichen Zustände. Alle Variablen sind Konstanten und alle Werte unveränderlich. Aber die State kann verwendet werden, um eine Programmierung mit veränderlichem Zustand zu emulieren:

add2 :: State Integer Integer
add2 = do
        -- add 1 to state
         x <- get
         put (x + 1)
         -- increment in another way
         modify (+1)
         -- return state
         get

evalState add2 7
=> 9

El add2 baut eine Monadenkette auf, die dann mit 7 als Ausgangszustand ausgewertet wird.

Offensichtlich ist dies etwas, das nur in Haskell Sinn macht. Andere Sprachen unterstützen veränderbare Zustände von Haus aus. Haskell ist im Allgemeinen eine "Opt-in"-Sprache - man aktiviert den veränderlichen Zustand, wenn man ihn braucht, und das Typsystem stellt sicher, dass der Effekt explizit ist. IO ist ein weiteres Beispiel dafür.

IO

El IO wird für die Verkettung und Ausführung von "unreinen" Funktionen verwendet.

Wie jede andere praktische Sprache hat auch Haskell eine Reihe von eingebauten Funktionen, die eine Schnittstelle zur Außenwelt bilden: putStrLine , readLine und so weiter. Diese Funktionen werden als "unrein" bezeichnet, weil sie entweder Seiteneffekte verursachen oder nicht-deterministische Ergebnisse haben. Selbst so etwas Einfaches wie das Abrufen der Uhrzeit gilt als unsauber, weil das Ergebnis nicht deterministisch ist - ein zweimaliger Aufruf mit denselben Argumenten kann unterschiedliche Werte ergeben.

Eine reine Funktion ist deterministisch - ihr Ergebnis hängt nur von den übergebenen Argumenten ab und hat neben der Rückgabe eines Wertes keine weiteren Auswirkungen auf die Umgebung.

Haskell fördert in hohem Maße die Verwendung von reinen Funktionen - dies ist ein Hauptargument für die Sprache. Leider brauchen Puristen einige unreine Funktionen, um etwas Sinnvolles zu tun. Der Haskell-Kompromiss besteht darin, reine und unreine Funktionen sauber zu trennen und zu garantieren, dass reine Funktionen auf keinen Fall unreine Funktionen ausführen können, weder direkt noch indirekt.

Dies wird dadurch gewährleistet, dass alle unreinen Funktionen den IO Typ. Der Einstiegspunkt in ein Haskell-Programm ist die main Funktion, die die IO Typ, so dass wir unreine Funktionen auf der obersten Ebene ausführen können.

Aber wie verhindert die Sprache, dass reine Funktionen unreine Funktionen ausführen? Das liegt an der "faulen" Natur von Haskell. Eine Funktion wird nur ausgeführt, wenn ihre Ausgabe von einer anderen Funktion verbraucht wird. Aber es gibt keine Möglichkeit, eine Funktion zu konsumieren IO Wert, außer um ihn zuzuweisen main . Wenn also eine Funktion eine unreine Funktion ausführen will, muss sie mit main und haben die IO Typ.

Die Verwendung der Monadenverkettung für IO-Operationen stellt auch sicher, dass sie in einer linearen und vorhersehbaren Reihenfolge ausgeführt werden, genau wie Anweisungen in einer imperativen Sprache.

Dies bringt uns zu dem ersten Programm, das die meisten Leute in Haskell schreiben werden:

main :: IO ()
main = do 
        putStrLn ”Hello World”

El do Schlüsselwort ist überflüssig, wenn es nur einen einzigen Vorgang gibt und daher nichts zu binden ist, aber ich behalte es trotzdem aus Gründen der Konsistenz.

El () Typ bedeutet "nichtig". Dieser spezielle Rückgabetyp ist nur für IO-Funktionen nützlich, die wegen ihrer Nebenwirkung aufgerufen werden.

Ein längeres Beispiel:

main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn "hello" ++ name

Dies führt zu einer Kette von IO Operationen, und da sie dem main Funktion, werden sie ausgeführt.

Vergleich von IO con Maybe zeigt die Vielseitigkeit des Monadenmusters. Für Maybe wird das Muster verwendet, um sich wiederholenden Code zu vermeiden, indem die bedingte Logik in die Bindungsfunktion verlagert wird. Für IO wird das Muster verwendet, um sicherzustellen, dass alle Operationen des IO Typ sequenziert sind und dass IO Operationen können nicht zu reinen Funktionen "durchsickern".

Resümee

Meiner subjektiven Meinung nach lohnt sich das Monadenmuster nur in einer Sprache, die eine eingebaute Unterstützung für das Muster bietet. Andernfalls führt es nur zu übermäßig verworrenem Code. Aber Haskell (und einige andere Sprachen) haben eine eingebaute Unterstützung, die die lästigen Teile versteckt, und dann kann das Muster für eine Vielzahl von nützlichen Dingen verwendet werden. Zum Beispiel:

  • Vermeidung von sich wiederholendem Code ( Maybe )
  • Hinzufügen von Sprachmerkmalen wie veränderbare Zustände oder Ausnahmen für abgegrenzte Bereiche des Programms.
  • Eklige Dinge von schönen Dingen trennen ( IO )
  • Eingebettete domänenspezifische Sprachen ( Parser )
  • Hinzufügen von GOTO in die Sprache.

87 Stimmen

Als jemand, der große Probleme hatte, Monaden zu verstehen, kann ich sagen, dass diese Antwort geholfen hat... ein wenig. Allerdings gibt es immer noch einige Dinge, die ich nicht verstehe. Auf welche Weise ist das Listenverständnis eine Monade? Gibt es eine erweiterte Form dieses Beispiels? Eine andere Sache, die mich an den meisten Monaden-Erklärungen stört, einschließlich dieser, ist, dass sie ständig "Was ist eine Monade?" mit "Wofür ist eine Monade gut?" und "Wie wird eine Monade implementiert?" verwechseln. Du bist über den Hai gesprungen, als du geschrieben hast "Eine Monade ist im Grunde nur ein Typ, der den >>= Operator unterstützt." Was mich gerade zum Nachdenken brachte...

11 Stimmen

Ich kratze mich am Kopf. Das scheint ein Implementierungsdetail zu sein, und es hilft mir nicht wirklich bei der Beantwortung der Frage "Warum sollte ich eine Monade verwenden". Es mag ja stimmen, aber die bisherige Erklärung hat mich nicht darauf vorbereitet. Ich dachte nicht: "Das ist in der Tat ein kniffliges Problem, denn was ich dafür bräuchte, ist eine Art Typ, der den >>=-Operator unterstützt. Oh hey, wie sich herausstellt, ist das eine Monade!" Nein, das ging mir nicht durch den Kopf, weil ich weder wusste, was ein >>=-Operator ist, noch wozu DAS gut ist, noch wurde mir ein Problem präsentiert, das damit gelöst werden kann.

4 Stimmen

Sie haben das natürlich später ein wenig nachgeholt, aber ich fürchte, ich weiß immer noch keine Antwort auf die Frage "Was ist eine Monade", geschweige denn eine einfache, prägnante Erklärung.

802voto

MathematicalOrchid Punkte 60602

Die Frage "Was ist eine Monade?" ist ein bisschen so, als würde man sagen: "Was ist eine Zahl?" Wir verwenden ständig Zahlen. Aber stellen Sie sich vor, Sie treffen jemanden, der keine Ahnung von Zahlen hat. Wie die 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 handelt sich um eine spezielle Art der Verkettung von Operationen.

Im Wesentlichen schreiben Sie Ausführungsschritte und verknüpfen sie mit der "Bind-Funktion". (In Haskell heißt sie >>= .) Sie können die Aufrufe an den Bindungsoperator selbst schreiben oder Sie können Syntaxzucker verwenden, der den Compiler veranlasst, diese Funktionsaufrufe für Sie einzufügen. In jedem Fall wird jeder Schritt durch einen Aufruf dieser Bindungsfunktion getrennt.

Die Bindungsfunktion ist also wie ein Semikolon; sie trennt die Schritte in einem Prozess. Die Aufgabe der Bindungsfunktion ist es, die Ausgabe des vorherigen Schritts zu übernehmen und in den nächsten Schritt einzuspeisen.

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

Nun, die Bindungsfunktion kann Sie nehmen einfach das Ergebnis eines Schritts und leiten es an den nächsten Schritt weiter. Aber wenn das "alles" ist, was die Monade tut... dann ist das nicht sehr nützlich. Und das ist wichtig zu verstehen: Jeder nützlich Monade tut etwas anderes darüber hinaus nur eine Monade zu sein. Jede nützlich Monade hat eine "besondere Fähigkeit", die sie einzigartig macht.

(Eine Monade, die nichts wird die "Identitätsmonade" genannt. Ähnlich wie die Identitätsfunktion klingt dies nach etwas völlig Sinnlosem, was es aber nicht ist... Aber das ist eine andere Geschichte™).

Im Grunde hat jede Monade ihre eigene Implementierung der Bindungsfunktion. Und man kann eine Bindungsfunktion so schreiben, dass sie zwischen den Ausführungsschritten verrückte Dinge tut. Zum Beispiel:

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

  • In Erweiterung dieser Idee können Sie "Ausnahmen" einführen. (Die Fehler Monade o Ausnahme Monade .) Da Sie sie selbst definieren und es sich nicht um eine Sprachfunktion handelt, können Sie festlegen, wie sie funktionieren. (Vielleicht wollen Sie z.B. die ersten beiden Ausnahmen ignorieren und nur abbrechen, wenn eine dritte Ausnahme ausgelöst wird).

  • Sie können jeden Schritt zurückgehen lassen mehrere Ergebnisse und lassen Sie die Bindungsfunktion in einer Schleife darüber laufen, so dass jeder Schritt in den nächsten Schritt übernommen wird. Auf diese Weise müssen Sie nicht ständig Schleifen schreiben, wenn Sie mit mehreren Ergebnissen arbeiten. Die Bindungsfunktion macht das alles "automatisch" für Sie. (Die Liste Monade .)

  • Neben der Übergabe eines "Ergebnisses" von einem Schritt an einen anderen können Sie die Bindungsfunktion zusätzliche Daten übergeben auch herum. Diese Daten erscheinen nun nicht mehr in Ihrem Quellcode, aber Sie können immer noch von überall darauf zugreifen, ohne sie manuell an jede Funktion übergeben zu müssen. (Die Leser Monade .)

  • Sie können es so einrichten, dass die "zusätzlichen Daten" ersetzt werden können. Dies ermöglicht es Ihnen destruktive Aktualisierungen zu simulieren ohne destruktive Aktualisierungen vorzunehmen. (Die Zustand Monade und sein Cousin, der Schriftsteller Monade .)

  • Denn Sie sind nur zu simulieren. destruktiven Aktualisierungen können Sie auf triviale Weise Dinge tun, die mit real zerstörerische Aktualisierungen. Sie können zum Beispiel die letzte Aktualisierung rückgängig machen , oder zu einer älteren Version zurückkehren .

  • Sie können eine Monade erstellen, in der Berechnungen durchgeführt werden können pausiert So können Sie Ihr Programm anhalten, an den internen Zustandsdaten herumspielen und es dann wieder fortsetzen.

  • Sie können "Fortsetzungen" als eine Monade implementieren. Dies ermöglicht es Ihnen den Verstand der Menschen brechen!

All dies und mehr ist mit Monaden möglich. Natürlich ist all dies auch perfekt möglich ohne auch Monaden. Es ist nur drastisch einfacher mit Monaden.

21 Stimmen

Ich schätze Ihre Antwort - insbesondere das letzte Zugeständnis, dass all dies natürlich auch ohne Monaden möglich ist. Ein wichtiger Punkt ist, dass es meist mit Monaden einfacher, aber oft nicht so effizient wie ohne sie. Sobald Sie Transformatoren einbeziehen müssen, hat die zusätzliche Schichtung von Funktionsaufrufen (und erstellten Funktionsobjekten) einen Preis, der schwer zu sehen und zu kontrollieren ist und durch eine clevere Syntax unsichtbar gemacht wird.

5 Stimmen

Zumindest in Haskell wird der größte Teil des Overheads von Monaden vom Optimierer entfernt. Die einzigen wirklichen "Kosten" liegen also in der benötigten Rechenleistung. (Das ist nicht unbedeutend, wenn "Wartbarkeit" etwas ist, das Ihnen wichtig ist.) Aber normalerweise machen Monaden Dinge einfacher , nicht härter. (Warum sollten Sie sich sonst die Mühe machen?)

0 Stimmen

Ich bin mir nicht sicher, ob Haskell dies unterstützt, aber mathematisch kann man eine Monade entweder in Form von >>= und return oder join und ap definieren. >>= und return machen Monaden praktisch nützlich, aber join und ap geben ein intuitiveres Verständnis davon, was eine Monade ist.

215voto

Arnar Punkte 550

Im Gegensatz zum allgemeinen Verständnis von Monaden haben sie nichts mit einem Zustand zu tun. Monaden sind einfach eine Möglichkeit, Dinge zu verpacken und Methoden bereitzustellen, um Operationen mit den verpackten Dingen durchzuführen, ohne sie zu entpacken.

In Haskell kann man zum Beispiel einen Typ erstellen, um einen anderen zu umhüllen:

data Wrapped a = Wrap a

Um Dinge zu verpacken, definieren wir

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

Um Operationen ohne Unwrapping durchzuführen, nehmen wir an, Sie haben eine Funktion f :: a -> b dann können Sie dies tun, um Aufzug die Funktion, die auf umgeschlagene Werte wirkt:

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

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

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

bind kann ein bisschen mehr tun als fmap , aber nicht umgekehrt. Eigentlich, fmap kann nur durch folgende Begriffe definiert werden bind y return . Wenn man also eine Monade definiert, gibt man ihren Typ an (hier war es Wrapped a ) und dann sagen, wie seine return y bind Operationen arbeiten.

Das Tolle daran ist, dass es sich dabei um ein so allgemeines Muster handelt, dass es überall auftaucht, wobei die reine Kapselung von Zuständen nur eines davon ist.

Ein guter Artikel darüber, wie Monaden verwendet werden können, um funktionale Abhängigkeiten einzuführen und so die Reihenfolge der Auswertung zu kontrollieren, wie es in der IO-Monade von Haskell geschieht, findet sich unter IO Innen .

Was das Verständnis von Monaden angeht, so sollten Sie sich nicht zu viele Gedanken darüber machen. Lesen Sie über sie, was Sie interessant finden, und machen Sie sich keine Sorgen, wenn Sie sie nicht sofort verstehen. Dann ist es der richtige Weg, einfach in eine Sprache wie Haskell einzutauchen. Monaden sind eines dieser Dinge, bei denen das Verständnis durch Übung in Ihr Gehirn sickert, und eines Tages merken Sie plötzlich, dass Sie sie verstehen.

0 Stimmen

-> ist rechts-assoziativ und spiegelt die Funktion Anwendung wider, die links-assoziativ ist, so dass das Weglassen der Klammern hier keinen Unterschied macht.

0 Stimmen

Ihre Erklärung hat mich überzeugt. Ich hätte allerdings eine begrenzte Summierung einiger Standardmonaden (Leser, Zustand, vielleicht, ...) hinzugefügt, um einige praktische Anwendungen und Umhüllungen zu illustrieren

5 Stimmen

Ich glaube nicht, dass dies eine sehr gute Erklärung ist. Monaden sind einfach ein Weg? okay, welcher Weg? Warum sollte ich nicht kapseln, indem ich eine Klasse statt einer Monade verwende?

180voto

nlucaroni Punkte 46744

Aber, Du hättest Monaden erfinden können!

sagt sigfpe:

Aber all dies führt Monaden als etwas Esoterisches ein, das einer Erklärung bedarf. Ich möchte jedoch argumentieren, dass sie überhaupt nicht esoterisch sind. In der Tat wären Sie angesichts verschiedener Probleme in der funktionalen Programmierung unweigerlich zu bestimmten Lösungen geführt worden, die allesamt Beispiele für Monaden sind. Ich hoffe sogar, dass ich Sie jetzt dazu bringen kann, sie zu erfinden, falls Sie das noch nicht getan haben. Es ist dann nur noch ein kleiner Schritt, um festzustellen, dass alle diese Lösungen in Wirklichkeit die gleiche Lösung in Verkleidung sind. Und nachdem Sie dies gelesen haben, sind Sie vielleicht besser in der Lage, andere Dokumente über Monaden zu verstehen, weil Sie alles, was Sie sehen, als etwas erkennen werden, das Sie bereits erfunden haben.

Viele der Probleme, die Monaden zu lösen versuchen, stehen im Zusammenhang mit dem Problem der Nebenwirkungen. Also fangen wir mit ihnen an. (Beachten Sie, dass Sie mit Monaden mehr tun können als nur Seiteneffekte zu behandeln, insbesondere können viele Arten von Container-Objekten als Monaden betrachtet werden. Einige der Einführungen in Monaden tun sich schwer damit, diese beiden unterschiedlichen Verwendungen von Monaden unter einen Hut zu bringen und konzentrieren sich nur auf die eine oder die andere).

In einer imperativen Programmiersprache wie C++ verhalten sich die Funktionen nicht wie die Funktionen der Mathematik. Nehmen wir zum Beispiel an, wir haben eine C++-Funktion, die ein einzelnes Fließkomma-Argument annimmt und ein Fließkomma-Ergebnis zurückgibt. Oberflächlich betrachtet mag das ein wenig wie eine mathematische Funktion aussehen, die reale Werte auf reale Werte abbildet, aber eine C++-Funktion kann mehr als nur eine Zahl zurückgeben, die von ihren Argumenten abhängt. Sie kann die Werte globaler Variablen lesen und schreiben sowie Ausgaben auf den Bildschirm schreiben und Eingaben des Benutzers entgegennehmen. In einer rein funktionalen Sprache hingegen kann eine Funktion nur das lesen, was ihr in ihren Argumenten geliefert wird, und die einzige Möglichkeit, wie sie die Welt beeinflussen kann, sind die Werte, die sie zurückgibt.

11 Stimmen

Die beste Art und Weise nicht nur im Internet, sondern überall. (Wadlers Originalarbeit Monaden für die funktionale Programmierung das ich in meiner Antwort weiter unten erwähnt habe, ist ebenfalls gut.) Keines der zahllosen Tutorials von Analogie kommt dem nahe.

19 Stimmen

Diese JavaScript-Übersetzung von Sigfpe's Beitrag ist der neue beste Weg, um Monaden zu lernen, für Leute, die nicht bereits fortgeschrittenes Haskell beherrschen!

2 Stimmen

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.

90voto

Chris Conway Punkte 54023

Eine Monade ist ein Datentyp, der zwei Operationen hat: >>= (alias bind ) und return (alias unit ). return nimmt einen beliebigen Wert und erzeugt damit eine Instanz der Monade. >>= nimmt eine Instanz der Monade und bildet eine Funktion auf sie ab. (Sie sehen bereits, dass eine Monade eine seltsame Art von Datentyp ist, da Sie in den meisten Programmiersprachen keine Funktion schreiben können, die einen beliebigen Wert annimmt und daraus einen Typ erzeugt. Monaden verwenden eine Art von parametrischer Polymorphismus .)

In Haskell-Notation wird die Monadenschnittstelle wie folgt 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 lediglich die Art und Weise, wie sich sinnvolle Implementierungen der Operationen verhalten sollten (im Grunde genommen, dass >>= y return sollten sich darüber einig sein, wie Werte in Monadeninstanzen umgewandelt werden und dass >>= ist assoziativ).

Bei Monaden geht es nicht nur um Zustand und E/A: Sie abstrahieren ein allgemeines Berechnungsmuster, das die Arbeit mit Zustand, E/A, Ausnahmen und Nicht-Determinismus 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

donde [] y : sind die Listenkonstrukteure, ++ ist der Verkettungsoperator, und Just y Nothing sind die Maybe Konstrukteure. Beide Monaden kapseln gängige und nützliche Berechnungsmuster für ihre jeweiligen Datentypen (beachten Sie, dass beide nichts mit Seiteneffekten oder E/A zu tun haben).

Man muss wirklich ein wenig herumspielen und nicht-trivialen Haskell-Code schreiben, um zu verstehen, worum es bei Monaden geht und warum sie nützlich sind.

0 Stimmen

Was genau meinen Sie mit "eine Funktion auf sie abbilden"?

0 Stimmen

Casebash, ich bin in der Einleitung absichtlich informell. Anhand der Beispiele am Ende des Textes können Sie sich ein Bild davon machen, was "eine Funktion abbilden" bedeutet.

3 Stimmen

Monad ist kein Datentyp. Es ist eine Regel für die Zusammenstellung von Funktionen: stackoverflow.com/a/37345315/1614973

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X