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

85voto

Apocalisp Punkte 34088

Sie sollten zunächst verstehen, was ein Funktor ist. Davor sollten Sie Funktionen höherer Ordnung verstehen.

A übergeordnete Funktion ist einfach eine Funktion, die eine Funktion als Argument annimmt.

A Funktor ist eine beliebige Typkonstruktion T für die es eine Funktion höherer Ordnung gibt, nennen wir sie map , die eine Funktion vom Typ a -> b (bei zwei beliebigen Typen a y b ) in eine Funktion T a -> T b . Diese map Funktion muss auch den Gesetzen der Identität und Komposition gehorchen, so dass die folgenden Ausdrücke für alle wahr sind p y q (Haskell-Notation):

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

Zum Beispiel kann ein Typkonstruktor namens List ist ein Funktor, wenn er mit einer Funktion des Typs (a -> b) -> List a -> List b die den oben genannten Gesetzen gehorcht. Die einzige praktische Umsetzung ist offensichtlich. Die daraus resultierende List a -> List b iteriert über die angegebene Liste und ruft die Funktion (a -> b) Funktion für jedes Element und gibt die Liste der Ergebnisse zurück.

A Monade ist im Grunde nur ein Funktor T mit zwei zusätzlichen Methoden, join vom Typ T (T a) -> T a y unit (manchmal auch als return , fork , oder pure ) des Typs a -> T a . Für Listen in Haskell:

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

Warum ist das nützlich? Weil man zum Beispiel.., map über eine Liste mit einer Funktion, die eine Liste zurückgibt. Join nimmt die resultierende Liste von Listen und verkettet sie. List ist eine Monade, weil dies möglich ist.

Sie können eine Funktion schreiben, die Folgendes tut map entonces join . Diese Funktion heißt bind , oder flatMap , oder (>>=) , oder (=<<) . Dies ist normalerweise die Art und Weise, wie eine Monadeninstanz in Haskell angegeben wird.

Eine Monade muss bestimmten Gesetzen genügen, nämlich dass join muss assoziativ sein. Das heißt, wenn Sie einen Wert haben x vom Typ [[[a]]] dann join (join x) sollte gleich sein join (map join x) . Und pure muss eine Identität sein für join tal que join (pure x) == x .

4 Stimmen

Geringfügiger Zusatz zu def of 'higher order function': sie können OR RETURN-Funktionen annehmen. Deshalb sind sie "höherwertig", weil sie Dinge mit sich selbst tun.

9 Stimmen

Nach dieser Definition ist die Addition eine Funktion höherer Ordnung. Sie nimmt eine Zahl und gibt eine Funktion zurück, die diese Zahl zu einer anderen addiert. Also nein, Funktionen höherer Ordnung sind ausschließlich Funktionen, deren Bereich aus Funktionen besteht.

0 Stimmen

Das Video ' Brian Beckman: Keine Angst vor der Monade ' folgt der gleichen Logik.

53voto

Aristotle Pagaltzis Punkte 106298

[Haftungsausschluss: Ich versuche immer noch, Monaden vollständig zu verstehen. Das Folgende ist nur das, was ich bis jetzt verstanden habe. Wenn es falsch ist, wird mich hoffentlich jemand, der sich auskennt, auf den Teppich holen].

Arnar schrieb:

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

Das ist genau das Richtige. Die Idee geht so:

  1. Man nimmt einen Wert und umhüllt ihn mit zusätzlichen Informationen. So wie der Wert von einer bestimmten Art ist (z.B. eine ganze Zahl oder eine Zeichenkette), so sind auch die zusätzlichen Informationen von einer bestimmten Art.

    Diese zusätzliche Information könnte z. B. ein Maybe oder ein IO .

  2. Dann gibt es einige Operatoren, die es ermöglichen, mit den umgeschlagenen Daten zu arbeiten und dabei die zusätzlichen Informationen mitzunehmen. Diese Operatoren verwenden die zusätzlichen Informationen, um zu entscheiden, wie das Verhalten der Operation auf den umschlossenen Wert geändert werden soll.

    Z.B. eine Maybe Int kann ein Just Int o Nothing . Wenn Sie nun eine Maybe Int zu einer Maybe Int prüft der Betreiber, ob sie beide Just Int ist, und wenn dies der Fall ist, wird die Int s, übergibt ihnen den Additionsoperator, wickelt die resultierende Int in eine neue Just Int (das ist ein gültiger Maybe Int ), und geben somit eine Maybe Int . Aber wenn einer von ihnen ein Nothing im Inneren, wird dieser Operator einfach sofort zurückgeben Nothing was wiederum eine gültige Maybe Int . Auf diese Weise können Sie vorgeben, dass Ihre Maybe Int s sind ganz normale Zahlen, mit denen man normal rechnen kann. Wenn Sie eine Nothing werden Ihre Gleichungen trotzdem das richtige Ergebnis liefern - ohne dass Sie Schecks auswerfen müssen für Nothing überall .

Aber das Beispiel ist genau das, was passiert, wenn Maybe . Wenn die zusätzliche Information eine IO dann ist der spezielle Operator, der für IO s aufgerufen werden, und es könnte etwas völlig anderes tun, bevor es die Addition durchführt. (OK, die Addition von zwei IO Int s zusammen ist wahrscheinlich unsinnig - ich bin mir noch nicht sicher.) (Außerdem, wenn Sie auf die Maybe Beispiel haben Sie bemerkt, dass das "Umhüllen eines Wertes mit zusätzlichen Elementen" nicht immer korrekt ist. Aber es ist schwer, genau, korrekt und präzise zu sein, ohne undurchschaubar zu sein).

Im Grunde genommen, "Monade" bedeutet in etwa "Muster". . Aber anstelle eines Buches voller informell erklärter und speziell benannter Muster haben Sie jetzt ein Sprachkonstrukt - Syntax und alles, was es Ihnen ermöglicht neue Muster als Dinge in Ihrem Programm deklarieren . (Die Ungenauigkeit hier ist, dass alle Muster einer bestimmten Form folgen müssen, so dass eine Monade nicht ganz so allgemein ist wie ein Muster. Aber ich denke, das ist der nächstliegende Begriff, den die meisten Leute kennen und verstehen).

Und das ist der Grund, warum die Menschen Monaden so verwirrend finden: weil sie ein so allgemeines Konzept sind. Die Frage, was etwas zu einer Monade macht, ist ähnlich vage wie die Frage, was etwas zu einem Muster macht.

Aber denken Sie an die Auswirkungen einer syntaktischen Unterstützung in der Sprache für die Idee eines Musters: Anstatt die Viererbande Buch und lernen Sie die Konstruktion eines bestimmten Musters auswendig, Sie müssen nur Code zu schreiben, der dieses Muster in einer agnostischen, generischen Weise implementiert einmal und dann sind Sie fertig! Sie können dann dieses Muster, wie Visitor oder Strategy oder Façade oder was auch immer, wiederverwenden, indem Sie die Operationen in Ihrem Code damit ausschmücken, ohne es immer wieder neu implementieren zu müssen!

Das ist der Grund, warum Menschen, die verstehen. Monaden finden sie so nützlich Es ist kein Konzept aus dem Elfenbeinturm, auf dessen Verständnis intellektuelle Snobs stolz sind (OK, das natürlich auch, teehee), sondern macht den Code tatsächlich einfacher.

18 Stimmen

Manchmal ist eine Erklärung von einem "Lernenden" (wie Ihnen) für einen anderen Lernenden relevanter als eine Erklärung von einem Experten. Lernende denken ähnlich :)

1 Stimmen

Was etwas zu einer Monade macht, ist das Vorhandensein einer Funktion mit dem Typ M (M a) -> M a . Die Tatsache, dass man das in eine der folgenden Arten verwandeln kann M a -> (a -> M b) -> M b ist das, was sie nützlich macht.

0 Stimmen

"Monade" bedeutet in etwa "Muster" ... nein.

50voto

Breton Punkte 14972

Nach langem Bemühen glaube ich, dass ich die Monade endlich verstanden habe. Nachdem ich meine eigene ausführliche Kritik an der Antwort, die mit überwältigender Mehrheit gewählt wurde, noch einmal gelesen habe, werde ich diese Erklärung anbieten.

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

  1. Wozu braucht man eine Monade?
  2. Was ist eine Monade?
  3. Wie wird eine Monade implementiert?

Wie ich in meinen ursprünglichen Kommentaren anmerkte, verfangen sich zu viele Erklärungen zu Monaden in Frage 3, ohne dass Frage 2 oder Frage 1 wirklich angemessen behandelt werden.

Wozu braucht man eine Monade?

Rein funktionale Sprachen wie Haskell unterscheiden sich von imperativen Sprachen wie C oder Java dadurch, dass ein rein funktionales Programm nicht unbedingt in einer bestimmten Reihenfolge, Schritt für Schritt, ausgeführt wird. Ein Haskell-Programm ist eher mit einer mathematischen Funktion vergleichbar, bei der man die "Gleichung" in einer beliebigen Anzahl von möglichen Reihenfolgen lösen kann. Dies bringt eine Reihe von Vorteilen mit sich, unter anderem, dass es die Möglichkeit bestimmter Arten von Fehlern ausschließt, insbesondere solche, die sich auf Dinge wie "Zustand" beziehen.

Es gibt jedoch bestimmte Probleme, die mit dieser Art der Programmierung nicht so einfach zu lösen sind. Einige Dinge, wie Konsolenprogrammierung und Dateieingabe/Ausgabe, müssen in einer bestimmten Reihenfolge ablaufen oder einen Zustand beibehalten. Eine Möglichkeit, mit diesem Problem umzugehen, besteht darin, eine Art Objekt zu schaffen, das den Zustand einer Berechnung repräsentiert, und eine Reihe von Funktionen, die ein Zustandsobjekt als Eingabe nehmen und ein neues, verändertes Zustandsobjekt zurückgeben.

Es ist nicht wichtig, wie dieser Wert genau aufgebaut ist, aber sagen wir, es ist ein Array von ASCII-Zeichen in Byte-Länge, das darstellt, was derzeit auf dem Bildschirm zu sehen ist, und ein Array, das die letzte vom Benutzer eingegebene Eingabezeile darstellt, in Pseudocode. Wir haben einige Funktionen definiert, die den Konsolenzustand annehmen, ihn verändern und einen neuen Konsolenzustand zurückgeben.

consolestate MyConsole = new consolestate;

Um also Konsolenprogrammierung zu betreiben, 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%!");

Die Programmierung auf diese Weise behält den "reinen" funktionalen Stil bei, während Änderungen an der Konsole in einer bestimmten Reihenfolge erfolgen müssen. Aber wir werden wahrscheinlich mehr als nur ein paar Operationen auf einmal durchführen wollen, wie im obigen Beispiel. Die Verschachtelung von Funktionen auf diese Weise wird langsam unübersichtlich. Was wir wollen, ist ein Code, der im Wesentlichen das Gleiche tut wie oben, aber ein bisschen mehr wie das hier 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. Aber wie machen wir das?

Was ist eine Monade?

Sobald Sie einen Typ (wie consolestate ), die 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), die automatisch die Rückgabewerte auf der linken Seite in die Funktionsparameter auf der rechten Seite einspeist, und eine lift Operator, der normale Funktionen in Funktionen verwandelt, die mit dieser speziellen Art von Bindungsoperator arbeiten.

Wie wird eine Monade implementiert?

Sehen Sie sich andere Antworten an, die sich anscheinend nicht zu sehr in Details verlieren wollen.

0 Stimmen

Die Sequenzierung ist nicht der einzige Grund für die Definition einer Monade. Eine Monade ist einfach ein beliebiger Funktor, der bind und return hat. Bind und return geben dir Sequenzierung. Aber sie bieten auch noch andere Dinge. Beachten Sie auch, dass Ihre bevorzugte imperative Sprache im Grunde eine ausgefallene IO-Monade mit OO-Klassen ist. Die einfache Definition von Monaden bedeutet, dass es einfach ist, das Interpreter-Muster zu verwenden - definieren Sie ein dsl als Monade und interpretieren Sie es!

0 Stimmen

44voto

George Punkte 1718

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 Funktionskompositionstechnik, die die Behandlung einiger Eingabeszenarien durch eine Kompositionsfunktion externalisiert, bind um die Eingabe während der Komposition vorzuverarbeiten.

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

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

Dieser Entwurf kann verbessert werden, indem die Eingabe so umstrukturiert wird, dass relevante Zustände leichter abgefragt werden können. Anstatt also einfach y kann der Wert zu Mb wie zum Beispiel, (is_OK, b) wenn y einen Begriff der Gültigkeit enthalten.

Wenn die Eingabe zum Beispiel nur eine Zahl sein kann, könnten Sie, anstatt eine Zeichenkette zurückzugeben, die pflichtgemäß eine Zahl enthalten kann oder nicht, den Typ in eine bool die das Vorhandensein einer gültigen Zahl und einer Zahl in einem Tupel wie, bool * float . Die zusammengesetzten Funktionen müssten nun nicht mehr eine Eingabezeichenkette analysieren, um festzustellen, ob eine Zahl existiert, sondern könnten lediglich die bool Teil eines Tupels.

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

Auch hier erfolgt die Zusammensetzung natürlich mit compose und so muss jede Funktion alle Szenarien ihrer Eingabe einzeln behandeln, obwohl dies jetzt viel einfacher ist.

Wie wäre es jedoch, wenn wir den Aufwand für Verhöre in den Fällen externalisieren könnten, in denen der Umgang mit einem Szenario Routine ist. Was wäre zum Beispiel, wenn unser Programm nichts tut, wenn die Eingabe nicht in Ordnung ist, z. B. wenn is_OK es false . Wenn dies der Fall wäre, müssten zusammengesetzte Funktionen dieses Szenario nicht selbst behandeln, was ihren Code drastisch vereinfacht und eine weitere Ebene der Wiederverwendung bewirkt.

Um diese Externalisierung zu erreichen, könnten wir eine Funktion verwenden, bind (>>=) zur Durchführung der composition anstelle von compose . Anstatt also einfach Werte vom Ausgang einer Funktion auf den Eingang einer anderen zu übertragen Bind würde die M Teil von Ma und entscheiden, ob und wie die zusammengesetzte Funktion auf die a . Natürlich ist die Funktion bind würde speziell für unseren Fall definiert werden M um seine Struktur zu untersuchen und jede beliebige Art von Anwendung durchführen zu können. Nichtsdestotrotz ist die a kann alles sein, da bind reicht lediglich die a ungeprüft an die zusammengesetzte Funktion, wenn sie die Anwendung für erforderlich hält. Außerdem müssen sich die zusammengesetzten Funktionen selbst nicht mehr mit dem M Teil der Eingabestruktur, wodurch sie vereinfacht werden. Folglich...

(a -> Mb) >>= (b -> Mc) oder knapper ausgedrückt Mb >>= (b -> Mc)

Kurz gesagt, eine Monade externalisiert und bietet damit ein Standardverhalten für die Behandlung bestimmter Eingabeszenarien, sobald die Eingabe so gestaltet ist, dass sie ausreichend offengelegt wird. Dieser Entwurf ist ein shell and content Modell, bei dem die Hülle Daten enthält, die für die Anwendung der zusammengesetzten Funktion relevant sind, und die nur von der bind Funktion.

Eine Monade besteht also aus drei Dingen:

  1. eine M Shell für die Aufnahme von monadenrelevanten Informationen,
  2. a bind eine Funktion, die so implementiert ist, dass sie diese Shell-Informationen bei der Anwendung der zusammengesetzten Funktionen auf den/die Inhaltswert(e), die sie in der Shell findet, verwendet, und
  3. komponierbare Funktionen der Form, a -> Mb und liefert Ergebnisse, die monadische Verwaltungsdaten enthalten.

Im Allgemeinen ist die Eingabe einer Funktion weitaus restriktiver als ihre Ausgabe, die z. B. Fehlerbedingungen enthalten kann; daher ist die Mb Ergebnisstruktur ist im Allgemeinen sehr nützlich. Der Divisionsoperator gibt zum Beispiel keine Zahl zurück, wenn der Divisor 0 .

Zusätzlich, monad s können Wrap-Funktionen enthalten, die Werte umbrechen, a in den monadischen Typ, Ma und allgemeine Funktionen, a -> b in monadische Funktionen, a -> Mb indem sie ihre Ergebnisse nach der Anwendung verpacken. Natürlich, wie bind sind solche Umbruchfunktionen spezifisch für M . Ein Beispiel:

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

Die Gestaltung der bind Funktion geht von unveränderlichen Datenstrukturen und reinen Funktionen aus andere Dinge werden komplex und können nicht garantiert werden. Als solche gibt es monadische Gesetze:

Da...

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)

Associativity bedeutet, dass bind behält die Reihenfolge der Bewertung bei, unabhängig davon, wann bind angewendet wird. Das heißt, in der Definition von Associativity oben, die Kraft frühe Bewertung der eingeklammerten binding de f y g führt nur zu einer Funktion, die erwartet Ma zur Vervollständigung der bind . Daher ist die Bewertung von Ma muss bestimmt werden, bevor sein Wert auf die folgenden Bereiche angewendet werden kann f und dieses Ergebnis wiederum gilt für g .

0 Stimmen

"...aber ich hoffe, andere finden es nützlich" es war in der Tat nützlich für mich, trotz all der betonten Sätze :D

0 Stimmen

Dies ist die prägnanteste und klarste Erklärung von Monaden, die ich je gelesen/gesehen/gehört habe. Vielen Dank dafür!

0 Stimmen

Es gibt einen wichtigen Unterschied zwischen Monade und Monoid. Monad ist eine Regel zum "Zusammensetzen" von Funktionen zwischen verschiedene Typen, so dass sie keine binäre Operation bilden, wie sie für Monoids erforderlich ist, siehe hier für weitere Einzelheiten: stackoverflow.com/questions/2704652/

35voto

Scott Wisniewski Punkte 23882

Eine Monade ist im Grunde eine Art "Typenoperator". Sie tut drei Dinge. Erstens wird ein Wert eines Typs in einen anderen Typ "verpackt" (oder anderweitig umgewandelt) (typischerweise ein "monadischer Typ" genannt). Zweitens macht er alle Operationen (oder Funktionen), die für den zugrunde liegenden Typ verfügbar sind, für den monadischen Typ verfügbar. Schließlich bietet sie Unterstützung für die Kombination ihrer selbst mit einer anderen Monade, um eine zusammengesetzte Monade zu erzeugen.

Die "maybe monad" ist im Wesentlichen das Äquivalent zu "nullable types" in Visual Basic / C#. Sie nimmt einen nicht löschbaren Typ "T" und konvertiert ihn in einen "Nullable<T>" und definiert dann, was alle binären Operatoren auf einem Nullable<T> bedeuten.

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

Sie werden aus mehreren Gründen als "Monaden" und nicht als "Typoperatoren" bezeichnet, was einfacher zu verstehen ist:

  1. Monaden haben Einschränkungen in Bezug auf das, was sie tun können (siehe die Definition für Details).
  2. Diese Einschränkungen und die Tatsache, dass es sich um drei Operationen handelt, entsprechen der Struktur einer sogenannten Monade in der Kategorientheorie, einem obskuren Zweig der Mathematik.
  3. Sie wurden von Befürwortern "reiner" funktionaler Sprachen entwickelt
  4. Befürworter rein funktionaler Sprachen mögen obskure Zweige der Mathematik
  5. Da die Mathematik obskur ist und Monaden mit bestimmten Programmierstilen assoziiert werden, neigen die Leute dazu, das Wort Monade als eine Art geheimen Handschlag zu verwenden. Aus diesem Grund hat sich niemand die Mühe gemacht, in einen besseren Namen zu investieren.

20 Stimmen

Seufz... Ich mache keinen Angriff auf Haskell ... Ich habe einen Scherz gemacht. Also, ich verstehe nicht wirklich das Stück über "ad hominem" zu sein. Ja, der Kalkül wurde "entworfen". Das ist der Grund, warum zum Beispiel Kalkulationsstudenten in der Leibniz-Notation unterrichtet werden und nicht in dem ekligen Zeug, das Netwton verwendet. Besseres Design. Gute Namen helfen dem Verständnis sehr. Wenn ich abelsche Gruppen "aufgeblähte Faltenhülsen" nennen würde, hättest du vielleicht Schwierigkeiten, mich zu verstehen. Sie würden vielleicht sagen: "Aber dieser Name ist Unsinn", niemand würde sie jemals so nennen. Für Leute, die noch nie etwas von Kategorientheorie gehört haben, klingt "Monade" wie Unsinn.

4 Stimmen

@Scott: Es tut mir leid, wenn meine ausführlichen Kommentare den Eindruck erweckt haben, dass ich Haskell gegenüber in die Defensive gegangen bin. Ich genieße Ihren Humor über den geheimen Handschlag und Sie werden feststellen, dass ich gesagt habe, dass es mehr oder weniger wahr ist :-) Wenn Sie Abelsche Gruppen als "aufgeblähte Faltenhülsen" bezeichnen würden, würden Sie den gleichen Fehler machen, wie wenn Sie versuchen würden, Monaden einen "besseren Namen" zu geben (vgl. F# "computation expressions"): der Begriff existiert und Leute, die sich dafür interessieren, wissen, was Monaden sind, aber nicht, was "warme, unscharfe Dinge" sind (oder "computation expressions"). Wenn ich Ihre Verwendung des Begriffs "Typoperator" richtig verstehe, gibt es viele andere Typoperatoren als Monaden.

1 Stimmen

5: Ein Name ist nur ein Name :D Aber um ehrlich zu sein, sehe ich nicht, wie man einen "besseren" Namen finden könnte, der irgendwie eine Bedeutung hat, ohne das Konzept der Monaden zu vernachlässigen, um das es geht.

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