Frage Was ist eine Monade?


Haskell kürzlich kurz angeschaut, was wäre ein kurz, prägnant, praktisch Erklärung, was eine Monade im Wesentlichen ist?

Ich habe die meisten Erklärungen gefunden, die mir ziemlich unzugänglich erschienen und denen es an praktischen Einzelheiten mangelt.


1227


Ursprung


Antworten:


Erstens: Der Begriff Monade ist ein bisschen leer, wenn Sie kein Mathematiker sind. Ein alternativer Begriff ist Berechnungsgenerator Das ist ein bisschen beschreibender für was sie eigentlich nützlich sind.

Sie fragen nach praktischen Beispielen:

Beispiel 1: Listenverständnis:

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

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

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

do
   x <- [1..10]
   if odd x 
       then [x * 2] 
       else []

Oder auch:

[1..10] >>= (\x -> if odd x then [x*2] else [])

Beispiel 2: Eingabe / Ausgabe:

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

Beide Beispiele verwenden Monaden, AKA-Berechnungs-Builder. Das gemeinsame Thema ist, dass die Monade Ketten Operationen auf eine bestimmte, nützliche Weise. Im Listenverständnis sind die Operationen verkettet, so dass, wenn eine Operation eine Liste zurückgibt, die folgenden Operationen ausgeführt werden jeder Artikel In der Liste. Die IO-Monade hingegen führt die Operationen sequentiell aus, übergibt jedoch eine "versteckte Variable", die "den Zustand der Welt" darstellt, was es uns ermöglicht, I / O-Code in einer rein funktionalen Weise zu schreiben.

Es ergibt sich das Muster von Verkettungsoperationen ist sehr nützlich und wird für viele verschiedene Dinge in Haskell verwendet.

Ein anderes Beispiel sind Ausnahmen: Verwendung der Error monad werden Operationen verkettet, so dass sie sequentiell ausgeführt werden, außer wenn ein Fehler ausgelöst wird. In diesem Fall wird der Rest der Kette abgebrochen.

Sowohl die Listenverständnis-Syntax als auch die Do-Notation sind syntaktische Zucker für Verkettungsoperationen unter Verwendung der >>= Operator. Eine Monade ist im Grunde nur eine Art, die das unterstützt >>= Operator.

Beispiel 3: Ein Parser

Dies ist ein sehr einfacher Parser, der entweder einen String 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, digitusw. sind ziemlich einfach. Sie stimmen entweder überein oder stimmen nicht überein. Die Magie ist die Monade, die den Kontrollfluss verwaltet: Die Operationen werden sequentiell ausgeführt, bis eine Übereinstimmung fehlschlägt, in welchem ​​Fall die Monade auf den neuesten Stand rückt <|> und versucht die nächste Option. Wieder eine Verkettung von Operationen mit einigen nützlichen Semantiken.

Beispiel 4: Asynchrone Programmierung

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

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 ist die Verwendung von GetResponseAsync - Es wartet 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. Das async Monade ist in der Lage, den Block selbst zu "spalten" und die Ausführung der zweiten Hälfte zu verschieben. (Das async {} Die Syntax gibt an, dass der Kontrollfluss im Block durch den Parameter async Monade.)

Wie sie arbeiten

Wie kann also eine Monade all diese phantastischen Kontrollfluss-Dinge tun? Was passiert eigentlich in einem Do-Block (oder a Berechnungsausdruck wie sie in F # genannt werden), ist, dass jede Operation (im Grunde jede Zeile) in einer separaten anonymen Funktion verpackt ist. Diese Funktionen werden dann mit den Funktionen kombiniert bind Betreiber (buchstabiert >>= in Haskell). Seit der bind Die Operation kombiniert Funktionen, sie kann sie ausführen, wie sie es für richtig hält: nacheinander, mehrmals, in umgekehrter Reihenfolge, verwerfen einige, führen einige in einem separaten Thread aus, wenn es sich anfühlt und so weiter.

Als Beispiel ist dies die erweiterte Version des IO-Codes aus Beispiel 2:

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

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

Beachten Sie, dass >>= ist für verschiedene Typen überladen, also hat jede Monade ihre eigene Implementierung von >>=. (Alle Operationen in der Kette müssen jedoch vom Typ der gleichen Monade sein, andernfalls >>= Betreiber wird nicht funktionieren.)

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, aber wie gesagt, macht das ganze Muster nützlich, wenn in der Implementierung der Monade etwas passiert >>=.

Es gibt einige zusätzliche Klugheit darin, wie die Werte von einer Operation zur nächsten weitergegeben werden, aber dies erfordert eine tiefere Erklärung des Haskell-Typ-Systems.

Zusammenfassen

In Haskell-Termen ist eine Monade ein parametrisierter Typ, der eine Instanz der Monad-Typklasse ist, die definiert >>= zusammen mit ein paar anderen Betreibern. Laien ist eine Monade nur eine Art, für die die >>= Operation ist definiert.

An sich >>= ist nur eine mühsame Art der Verkettung von Funktionen, aber mit der Anwesenheit der Do-Notation, die die "Rohrleitungen" versteckt, erweist sich die monadischen Operationen als eine sehr nette und nützliche Abstraktion, nützlich viele Orte in der Sprache, und nützlich für die Erstellung Ihre eigenen Mini-Sprachen in der Sprache.

Warum sind Monaden hart?

Für viele Haskell-Lernende sind Monaden ein Hindernis, das sie wie eine Ziegelmauer 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. basiert. Das Problem ist, dass Haskell I / O auf Monaden basiert, und I / O ist wahrscheinlich eines der ersten Dinge, die Sie beim Lernen einer neuen Sprache verstehen wollen - schließlich macht es nicht viel Spaß, Programme zu erstellen, die keine erzeugen Ausgabe. Ich habe keine unmittelbare Lösung für dieses Henne-und-Ei-Problem, außer I / O wie "Magie passiert hier" zu behandeln, 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


965



"Was ist eine Monade" zu erklären, ist ein bisschen wie "Was ist eine Nummer?" Wir benutzen immer Nummern. Aber stell dir vor, du hast jemanden getroffen, der nichts über Zahlen wusste. Wie zum Teufel würdest du erklären, was Zahlen sind? Und wie würdest du überhaupt anfangen zu 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 an den Bindeoperator selbst schreiben, oder Sie können die Syntax Zucker verwenden, wodurch der Compiler diese Funktionsaufrufe für Sie einfügt. Wie auch immer, jeder Schritt wird durch einen Aufruf dieser Bindefunktion getrennt.

Also ist die Bindefunktion wie ein Semikolon; Es trennt die Schritte in einem Prozess. Der Job der Bind-Funktion besteht darin, die Ausgabe des vorherigen Schritts zu übernehmen und sie in den nächsten Schritt einzufügen.

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

Nun, die Bindefunktion kann Nehmen Sie einfach das Ergebnis aus einem Schritt und füttern Sie es mit dem nächsten Schritt. Aber wenn das alles ist, macht die Monade ... das ist eigentlich nicht sehr nützlich. Und das ist wichtig zu verstehen: Jeder sinnvoll Monade macht etwas anderes in Ergänzung nur eine Monade zu sein. Jeden sinnvoll Monad hat eine "besondere Kraft", die es einzigartig macht.

(Eine Monade, die das tut nichts Special heißt die "Identitätsmonade". Ähnlich wie die Identitätsfunktion klingt das wie eine völlig sinnlose Sache, stellt sich jedoch heraus, nicht zu sein ... Aber das ist eine andere Geschichte ™.)

Grundsätzlich hat jede Monade ihre eigene Implementierung der Bind-Funktion. Und Sie können eine Bindefunktion schreiben, so dass zwischen den Ausführungsschritten Dinge gebohrt werden. Beispielsweise:

  • Wenn jeder Schritt einen Erfolgs- / Fehlerindikator zurückgibt, können Sie den nächsten Schritt nur ausführen, wenn der vorherige Schritt erfolgreich war. Auf diese Weise bricht ein fehlgeschlagener Schritt die gesamte Sequenz "automatisch" ab, ohne dass Sie eine bedingte Prüfung von Ihnen durchführen müssen. (Das Fehler Monade.)

  • Erweitern Sie diese Idee, können Sie "Ausnahmen" implementieren. (Das Fehler Monad oder Ausnahme Monade.) Da Sie sie selbst definieren und nicht als Sprachfunktion, können Sie definieren, wie sie funktionieren. (Z. B. möchten Sie vielleicht die ersten beiden Ausnahmen ignorieren und nur abbrechen, wenn a dritte Ausnahme wird ausgelöst.)

  • Sie können jeden Schritt zurückkehren lassen mehrere Ergebnisse, und binden Sie die Bindefunktion über sie, und füttern Sie jeden in den nächsten Schritt für Sie. 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. (Das Liste Monad.)

  • Sie können nicht nur ein "Ergebnis" von einem Schritt an einen anderen übergeben, sondern auch die Bindefunktion Übergeben Sie zusätzliche Daten auch herum. Diese Daten werden jetzt nicht in Ihrem Quellcode angezeigt, Sie können jedoch von überall auf sie zugreifen, ohne sie manuell an jede Funktion übergeben zu müssen. (Das Leser Monad.)

  • Sie können es so einrichten, dass die "Extra-Daten" ersetzt werden können. Dies ermöglicht es Ihnen simulieren Sie zerstörerische Updates, ohne eigentlich destruktive Updates zu machen. (Das Staat Monad und sein Cousin der Schriftsteller Monad.)

  • Weil du nur bist simulierend destruktive Updates, können Sie trivial Dinge tun, mit denen es unmöglich wäre echt zerstörerische Updates. Zum Beispiel können Sie mache das letzte Update rückgängig, 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, mit internen Statusdaten arbeiten und daran arbeiten und es dann fortsetzen.

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

All das und mehr ist mit Monaden möglich. Das alles ist natürlich auch möglich ohne Monaden auch. Es ist nur drastisch einfacher Monaden verwenden.


638



Im Gegensatz zum allgemeinen Verständnis der Monaden haben sie mit dem Staat nichts zu tun. Monaden sind einfach eine Möglichkeit, Dinge zu umhüllen und Methoden bereitzustellen, um Operationen an dem umhüllten Material durchzuführen, ohne es auszupacken.

Beispielsweise können Sie in Haskell einen Typ zum Umbrechen eines anderen Typs erstellen:

data Wrapped a = Wrap a

Um Dinge zu verpacken, die wir definieren

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

Um Operationen ohne Auspacken auszuführen, sagen Sie, dass Sie eine Funktion haben f :: a -> bDann kannst du das tun Aufzug Diese Funktion, um auf umbrochene Werte zu reagieren:

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

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

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

bind kann ein bisschen mehr als fmapaber nicht umgekehrt. Tatsächlich, fmap kann nur in Bezug auf definiert werden bind und return. Also, wenn Sie eine Monade definieren, geben Sie ihren Typ an (hier war es) Wrapped a) und dann sag wie es ist return und bind Operationen arbeiten.

Die coole Sache ist, dass sich herausstellt, dass dies ein so allgemeines Muster ist, dass es überall auftaucht. Die Verkapselung des Zustands auf eine reine Art ist nur eine davon.

Für 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, sollten Sie nachsehen IO nach innen.

Um Monaden zu verstehen, mach dir keine Sorgen. Lesen Sie über sie, was Sie interessant finden und machen Sie sich keine Sorgen, wenn Sie nicht sofort verstehen. Dann ist Tauchen in einer Sprache wie Haskell der richtige Weg. Monaden sind eines dieser Dinge, bei denen das Verstehen durch Übung in dein Gehirn eindringt, eines Tages merkst du plötzlich, dass du sie verstehst.


164



Aber, Sie könnten Monaden erfunden haben!

sigfpe sagt:

All das führt Monaden als etwas Esoterisches ein, das erklärungsbedürftig ist. Aber ich möchte behaupten, dass sie überhaupt nicht esoterisch sind. Tatsächlich wären Sie angesichts verschiedener Probleme in der funktionalen Programmierung unweigerlich 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 nicht schon getan haben. Es ist dann ein kleiner Schritt, um zu bemerken, dass alle diese Lösungen tatsächlich die gleiche Lösung darstellen. Und nachdem Sie dies gelesen haben, sind Sie möglicherweise besser in der Lage, andere Dokumente auf Monaden zu verstehen, weil 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 monads mehr tun können, als Nebenwirkungen zu behandeln, insbesondere können viele Arten von Container-Objekten als Monaden betrachtet werden. Einige der Einführungen in Monaden finden es schwierig, diese beiden unterschiedlichen Anwendungen von Monaden in Einklang zu bringen und sich auf nur eines 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 Gleitkomma-Argument und ein Gleitkomma-Ergebnis zurückgibt. Oberflächlich gesehen mag es ein wenig wie eine mathematische Funktion aussehen, die Realen zu Realen zuordnet, aber eine C ++ - Funktion kann mehr, als nur eine Zahl zurückgeben, die von ihren Argumenten abhängt. Es kann die Werte von globalen Variablen lesen und schreiben sowie die Ausgabe 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 Art, wie sie sich auf die Welt auswirken kann, ist durch die Werte, die sie zurückgibt.


161



Eine Monade ist ein Datentyp, der zwei Operationen hat: >>= (aka bind) und return (aka unit). return nimmt einen beliebigen Wert und erzeugt damit eine Instanz der Monade. >>= nimmt eine Instanz der Monade und bildet eine Funktion darüber ab. (Sie können schon sehen, 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 der Haskell-Notation wird die Monad-Schnittstelle 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 die Art und Weise, wie sich sinnvolle Implementierungen der Operationen verhalten sollten (im Grunde genommen das >>= und return sollte darüber einig sein, wie Werte in Monaden Instanzen und das umgewandelt werden >>= 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 einfachsten 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

woher [] und : sind die Listenkonstruktoren, ++ist der Verkettungsoperator und Just und Nothing sind die Maybe Konstruktoren. Diese beiden Monaden kapseln gemeinsame und nützliche Berechnungsmuster für ihre jeweiligen Datentypen ein (beachten Sie, dass nichts mit Nebenwirkungen oder E / A zu tun hat).

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


77



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

EIN Funktion höherer Ordnung ist einfach eine Funktion, die eine Funktion als Argument übernimmt.

EIN Funktor ist jede Art von Konstruktion T für die es eine Funktion höherer Ordnung gibt, rufen Sie sie auf map, das transformiert eine Funktion des Typs a -> b (Gegeben zwei Arten a und b) in eine Funktion T a -> T b. Dies map Die Funktion muss auch die Gesetze der Identität und Zusammensetzung befolgen, so dass die folgenden Ausdrücke für alle wahr sind p und q (Haskell-Notation):

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

Zum Beispiel ein Typkonstruktor namens List Ist ein Funktor, wenn er mit einer Funktion vom Typ ausgestattet ist (a -> b) -> List a -> List b was den obigen Gesetzen gehorcht. Die einzige praktische Implementierung ist offensichtlich. Das Ergebnis List a -> List b Funktion iteriert über die angegebene Liste und ruft die (a -> b) Funktion für jedes Element und gibt die Liste der Ergebnisse zurück.

EIN Monade ist im Wesentlichen nur ein Funktor T mit zwei zusätzlichen Methoden, joinvom Typ T (T a) -> T a, und unit (manchmal genannt return, fork, oder pure) vom Typ a -> T a. Für Listen in Haskell:

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

Warum ist das nützlich? Weil du 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 das möglich ist.

Sie können eine Funktion schreiben, die dies tut map, dann join. Diese Funktion wird aufgerufen bind, oder flatMap, oder (>>=), oder (=<<). Normalerweise wird eine Monad-Instanz in Haskell angegeben.

Eine Monade muss bestimmte Gesetze erfüllen, nämlich das join muss assoziativ sein. Das bedeutet, 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 so dass join (pure x) == x.


70



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

Arnar schrieb:

Monaden sind einfach eine Möglichkeit, Dinge zu umhüllen und Methoden bereitzustellen, um Operationen an dem umhüllten Material durchzuführen, ohne es auszupacken.

Genau das ist es. Die Idee läuft so:

  1. Sie nehmen irgendeinen Wert und wickeln es mit einigen zusätzlichen Informationen ein. Genauso wie der Wert einer bestimmten Art ist (zB eine ganze Zahl oder eine Zeichenkette), so ist die zusätzliche Information von einer bestimmten Art.

    Z. B. könnte diese zusätzliche Information eine sein Maybe oder 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 umbrochenen Wert geändert werden soll.

    Zum Beispiel a Maybe Int kann ein sein Just Int oder Nothing. Jetzt, wenn Sie ein hinzufügen Maybe Int zu einem Maybe Intüberprüft der Bediener, ob beide vorhanden sind Just Ints im Inneren, und wenn ja, wird das auspacken Ints, übergeben Sie ihnen den Additionsoperator, wickeln Sie das Ergebnis um Int in ein neues Just Int (Das ist ein gültiger Maybe Int), und somit a zurückgeben Maybe Int. Aber wenn einer von ihnen ein war Nothing Im Inneren kehrt dieser Operator sofort zurück Nothing, was wiederum eine gültige ist Maybe Int. Auf diese Weise können Sie so tun, als ob Maybe Ints sind nur normale Zahlen und führen sie regelmäßig mathematisch aus. Wenn du einen bekommen würdest Nothing, Ihre Gleichungen werden immer noch das richtige Ergebnis liefern - ohne dass du Schecks einwerfen musst Nothing überall.

Aber das Beispiel ist genau das, was passiert Maybe. Wenn die zusätzliche Information ein war IO, dann ist dieser spezielle Operator definiert für IOs würde stattdessen aufgerufen werden, und es könnte etwas völlig anderes tun, bevor die Addition durchgeführt wird. (OK, zwei hinzufügen IO Ints zusammen ist wahrscheinlich unsinnig - ich bin mir noch nicht sicher.) (Auch wenn Sie auf die Maybe Sie haben beispielsweise festgestellt, dass das "Einpacken eines Wertes mit zusätzlichen Daten" nicht immer korrekt ist. Aber es ist schwer, genau, richtig und präzise zu sein, ohne unergründlich zu sein.

Grundsätzlich gilt, "Monade" bedeutet grob "Muster". Aber statt eines Buches voller informell erklärter und speziell benannter Muster hast du es jetzt ein Sprachkonstrukt - Syntax und alles - damit können Sie Deklariere neue Muster als Dinge in deinem Programm. (Die Ungenauigkeit hier ist, dass alle Muster einer bestimmten Form folgen müssen, also ist eine Monade nicht so allgemein wie ein Muster. Aber ich denke, das ist der nächste Begriff, den die meisten Menschen kennen und verstehen.)

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

Aber denken Sie an die Implikationen der syntaktischen Unterstützung in der Sprache für die Idee eines Musters: anstatt die Gruppe von vier Buch und merke dir die Konstruktion eines bestimmten Musters, du nur schreibe Code, der dieses Muster auf agnostische, generische Weise implementiert einmal und dann bist du fertig! Sie können dieses Muster wie Besucher oder Strategie oder Fassade oder was auch immer wiederverwenden, indem Sie einfach die Vorgänge in Ihrem Code damit dekorieren, ohne es immer wieder neu implementieren zu müssen!

Deshalb Leute, die verstehen Monaden finden sie so sinnvoll: Es ist nicht irgendein Elfenbeinturm-Konzept, dass intellektuelle Snobs sich auf das Verständnis stossen (OK, das ist natürlich, teehee), sondern macht eigentlich den Code einfacher.


42



Nach langem Bemühen glaube ich, die Monade zu verstehen. Nachdem ich meine eigene langwierige Kritik an der überwältigend gut gewählten 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 angemerkt habe, werden zu viele Monade-Erklärungen in Frage Nr. 3, ohne und vor der richtigen Beantwortung von Frage 2 oder Frage 1, aufgegriffen.

Warum brauchst du eine Monade?

Reine funktionale Sprachen wie Haskell unterscheiden sich von imperativen Sprachen wie C oder Java darin, dass ein reines funktionales Programm nicht notwendigerweise in einer bestimmten Reihenfolge Schritt für Schritt ausgeführt wird. Ein Haskell-Programm ähnelt eher einer mathematischen Funktion, in der Sie die "Gleichung" in einer beliebigen Anzahl potentieller Ordnungen lösen können. Dies bringt eine Reihe von Vorteilen, unter anderem, dass es die Möglichkeit bestimmter Arten von Fehlern beseitigt, insbesondere solche, die sich auf Dinge wie "Staat" beziehen.

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

Lassen Sie uns also einen hypothetischen "state" -Wert erstellen, der den Zustand eines Konsolenbildschirms darstellt. genau, wie dieser Wert konstruiert wird, ist nicht wichtig, aber sagen wir, es ist ein Array von Bytes Länge ASCII-Zeichen, die das, was derzeit auf dem Bildschirm sichtbar ist, und ein Array, das die letzte Zeile der Eingabe durch den Benutzer in Pseudocode darstellt. Wir haben einige Funktionen definiert, die den Konsolenstatus annehmen, ihn ändern und einen neuen Konsolenstatus zurückgeben.

consolestate MyConsole = new consolestate;

Um Konsolen-Programmierung zu machen, müssten Sie jedoch in einer rein funktionalen Weise viele Funktionsaufrufe ineinander verschachteln.

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

Das Programmieren auf diese Weise behält den "reinen" funktionalen Stil bei, während er Änderungen an der Konsole in einer bestimmten Reihenfolge erzwingt. Aber wir werden wahrscheinlich mehr als nur ein paar Operationen gleichzeitig machen wollen, wie im obigen Beispiel. Nesting-Funktionen auf diese Weise beginnen, plump zu werden. Was wir wollen, ist Code, der im Wesentlichen das Gleiche wie oben tut, aber ein bisschen mehr wie folgt geschrieben ist:

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

Dies wäre in der Tat eine bequemere Möglichkeit, es zu schreiben. Wie machen wir das?

Was ist eine Monade?

Sobald Sie einen Typ haben (z. B. 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 definieren : (bind), die automatisch Rückgabewerte auf der linken Seite in Funktionsparameter auf der rechten Seite eingibt, und a lift Operator, der normale Funktionen in Funktionen umwandelt, die mit dieser spezifischen Art von Bindeoperator arbeiten.

Wie wird eine Monade implementiert?

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


37



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

Eine gute Motivation für Monads ist Sigfpe (Dan Piponi) Sie könnten Monaden erfunden haben! (Und vielleicht hast du schon). Es gibt eine Menge anderer Monad Tutorials, von denen viele irrtümlicherweise versuchen, Monaden in "einfachen Begriffen" zu erklären, indem sie verschiedene Analogien verwenden: dies ist die Monade Tutorial Fehlschlag; vermeide sie.

Wie DR MacIver sagt in Sag uns, warum deine Sprache saugt:

Also, Dinge, die ich an Haskell hasse:

    Beginnen wir mit dem Offensichtlichen. Monad Tutorials. Nein, keine Monaden. Speziell die Tutorials. Sie sind endlos, übertrieben und lieber Gott, sind sie langweilig. Außerdem habe ich nie einen überzeugenden Beweis dafür gesehen, dass sie tatsächlich helfen. Lies die Klassendefinition, schreibe etwas Code, übertreibe den unheimlichen Namen.

Du sagst du verstehst die Maybe Monade? Gut, du bist auf dem Weg. Beginnen Sie einfach mit anderen Monaden und Sie werden früher oder später verstehen, was Monaden im Allgemeinen sind.

[Wenn Sie mathematisch orientiert sind, möchten Sie vielleicht die Dutzenden von Tutorials ignorieren und die Definition lernen oder folgen Vorlesungen in der Kategorie Theorie :) Der Hauptteil der Definition ist, dass eine Monade M einen "Typkonstruktor" beinhaltet, der für jeden existierenden Typ "T" einen neuen Typ "MT" definiert, und einige Wege zwischen "regulären" Typen und "M" hin und her zu gehen Arten.]

Überraschenderweise ist eine der besten Einführungen in Monaden tatsächlich eine der frühen wissenschaftlichen Arbeiten, die Monaden, Philip Wadlers, vorstellen Monaden für die funktionale Programmierung. Es hat tatsächlich praktische, nicht trivial motivierende Beispiele, im Gegensatz zu vielen der künstlichen Tutorials da draußen.


33