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

5voto

Jonas Punkte 534

Ich werde versuchen zu erklären Monad im Kontext von Haskell.

In der funktionalen Programmierung ist die Funktionskomposition wichtig. Sie ermöglicht es unserem Programm, aus kleinen, leicht zu lesenden Funktionen zu bestehen.

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

Wir können (f . g) x was genau dasselbe ist wie f (g x) , donde x ist ein Int Wert.

Bei der Komposition/Anwendung des Ergebnisses einer Funktion auf eine andere ist es wichtig, dass die Typen übereinstimmen. Im obigen Fall ist der Typ des Ergebnisses, das von g muss der gleiche Typ sein wie der, der von f .

Aber manchmal stehen Werte in Kontexten, und das macht es etwas schwieriger, Typen aneinanderzureihen. (Werte in Kontexten zu haben ist sehr nützlich. Zum Beispiel kann die Maybe Int Typ stellt eine Int Wert, der möglicherweise nicht vorhanden ist, die IO String Typ repräsentiert eine String Wert, der als Ergebnis der Durchführung einiger Nebeneffekte vorhanden ist).

Nehmen wir an, wir haben jetzt g1 :: Int -> Maybe String y f1 :: String -> Maybe Bool . g1 y f1 sind sehr ähnlich zu g y f beziehungsweise.

Das können wir nicht tun (f1 . g1) x o f1 (g1 x) , donde x ist ein Int Wert. Der Typ des Ergebnisses, das von g1 ist nicht das, was f1 erwartet.

Wir könnten komponieren f y g mit dem . Operator, aber jetzt können wir nicht komponieren f1 y g1 con . . Das Problem ist, dass wir einen Wert in einem Kontext nicht ohne weiteres an eine Funktion übergeben können, die einen Wert erwartet, der nicht in einem Kontext steht.

Wäre es nicht schön, wenn wir einen Operator einführen würden, der die g1 y f1 , so dass wir schreiben können (f1 OPERATOR g1) x ? g1 gibt einen Wert in einem Kontext zurück. Der Wert wird aus dem Kontext genommen und auf f1 . Und ja, wir haben einen solchen Betreiber. Es ist <=< .

Wir haben auch die >>= Operator, der für uns genau das Gleiche tut, wenn auch in einer etwas anderen Syntax.

Wir schreiben: g1 x >>= f1 . g1 x ist eine Maybe Int Wert. Die Website >>= Operator trägt dazu bei, dass Int Wert aus dem "vielleicht-nicht-da"-Kontext herausnehmen und ihn auf f1 . Das Ergebnis von f1 die ein Maybe Bool wird das Ergebnis der gesamten >>= Betrieb.

Und schließlich, warum ist Monad nützlich? Weil Monad ist die Typklasse, die die >>= Operator, ganz ähnlich wie der Eq Typklasse, die die == y /= Betreiber.

Zusammenfassend lässt sich sagen, dass die Monad Typklasse definiert die >>= Operator, der es uns ermöglicht, Werte in einem Kontext (wir nennen sie monadische Werte) an Funktionen zu übergeben, die keine Werte in einem Kontext erwarten. Um den Kontext wird sich dann gekümmert.

Wenn es hier etwas zu beachten gibt, dann ist es das Monad s erlauben die Komposition von Funktionen, die Werte in Kontexten beinhalten .

0 Stimmen

0 Stimmen

IOW, Monad ist ein verallgemeinertes Funktionsaufrufprotokoll.

0 Stimmen

Ihre Antwort ist meiner Meinung nach die hilfreichste. Obwohl ich sagen muss, dass ich denke, dass die Betonung auf der Tatsache liegen muss, dass die Funktionen, auf die Sie sich beziehen, nicht nur Werte in Kontexte einbeziehen, sondern aktiv Werte in Kontexte setzen. So würde zum Beispiel eine Funktion f :: m a -> m b sehr leicht mit einer anderen Funktion g :: m b -> m c komponiert werden können. Aber Monaden (speziell bind) erlauben es uns, Funktionen, die ihre Eingabe in denselben Kontext stellen, ständig zu komponieren, ohne dass wir den Wert zuerst aus diesem Kontext herausnehmen müssen (was effektiv Informationen aus dem Wert entfernen würde)

5voto

cjs Punkte 23476

Die beiden Dinge, die mir am meisten geholfen haben, als ich mich dort informierte, waren:

Kapitel 8, "Functional Parsers", aus dem Buch von Graham Hutton Programmieren in Haskell . Hier werden Monaden eigentlich überhaupt nicht erwähnt, aber wenn Sie das Kapitel durcharbeiten und wirklich alles darin verstehen, insbesondere wie eine Folge von Bindungsoperationen ausgewertet wird, werden Sie die Interna von Monaden verstehen. Rechnen Sie damit, dass Sie dafür mehrere Anläufe brauchen.

Das Lernprogramm Alles über Monaden . Darin werden mehrere gute Beispiele für ihre Verwendung gegeben, und ich muss sagen, dass die Analogie in Anhang I für mich funktioniert hat.

5voto

Jordan Punkte 4330

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

Betrachten Sie diese drei Funktionen in Pseudocode:

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

f nimmt ein geordnetes Paar der Form <x, messages> und gibt ein geordnetes Paar zurück. Es lässt das erste Element unberührt und fügt "called f. " zum zweiten Punkt. Dasselbe gilt für g .

Sie können diese Funktionen zusammenstellen und erhalten Ihren ursprünglichen Wert zusammen mit einer Zeichenkette, 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. ">

Ihnen missfällt die Tatsache, dass f y g sind für das Anhängen ihrer eigenen Protokollmeldungen an die vorherigen Protokollierungsinformationen verantwortlich. (Stellen Sie sich der Einfachheit halber vor, dass anstelle des Anhängens von Strings, f y g muss eine komplizierte Logik für das zweite Element des Paares ausführen. Es wäre mühsam, diese komplizierte Logik in zwei - oder mehr - verschiedenen Funktionen zu wiederholen).

Sie ziehen es vor, einfachere Funktionen zu schreiben:

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

Aber sieh dir an, was passiert, wenn du sie komponierst:

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

Das Problem ist, dass vorbei an ein Paar in eine Funktion umwandelt, erhält man nicht das, was man will. Aber was wäre, wenn Sie Futtermittel 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 "Futtermittel m en f ". An Futtermittel ein Paar <x, messages> in eine Funktion f soll Pass x en f erhalten <y, message> aus f und zurück <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 einpacken und dann Futtermittel 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)

Das ist dasselbe wie vorbei an den Wert in die Funktion.

Zweitens: Wenn Sie ein Paar 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 den beiden.

Drittens: Wenn Sie eine Funktion definieren, die x und füttert g(x) en f :

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

und füttern Sie sie mit einem Paar:

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

Das ist dasselbe wie die Eingabe des Paares in g und Einspeisung des resultierenden Paares in f .

Sie haben das meiste von einer Monade. Jetzt müssen Sie nur noch die Datentypen in Ihrem Programm kennen.

Welche Art von Wert ist <x, "called f. "> ? Nun, das hängt davon ab, welche Art von Wert x ist. Wenn x ist vom Typ t dann ist Ihr Paar ein Wert vom Typ "Paar von t und String". Nennen Sie diesen Typ M t .

M ist ein Typkonstruktor: M allein verweist nicht auf einen Typ, sondern M _ bezieht sich auf einen Typ, sobald Sie das Feld mit einem Typ ausfüllen. Eine M int ist ein Paar aus einem int und einer Zeichenkette. Eine M string ist ein Paar aus einem String und einem String. Etc.

Glückwunsch, Sie haben eine Monade erstellt!

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

Eine Monade ist ein Tupel <M, feed, wrap> où :

  • M ist ein Typkonstruktor.
  • feed nimmt eine (Funktion, die eine t und gibt eine M u ) und ein M t und gibt eine M u .
  • wrap nimmt eine v und gibt eine M v .

t , u y v sind drei beliebige Typen, die gleich oder verschieden sein können. Eine Monade erfüllt die drei Eigenschaften, die Sie für Ihre spezifische Monade nachgewiesen haben:

  • Fütterung eine verpackte t in eine Funktion ist dasselbe wie vorbei an die unverpackte t in die Funktion.

    Förmlich: feed(f, wrap(x)) = f(x)

  • Fütterung eines M t en wrap hat keine Auswirkungen auf die M t .

    Förmlich: feed(wrap, m) = m

  • Fütterung eines M t (nennen Sie es m ) in eine Funktion, die

    • geht die t en g
    • erhält eine M u (nennen Sie es n ) von g
    • Feeds n en f

    ist dasselbe wie

    • Fütterung m en g
    • unter n de g
    • Fütterung n en f

    Förmlich: feed(h, m) = feed(f, feed(g, m)) wobei h(x) := feed(f, g(x))

Typischerweise, feed heißt bind (AKA >>= in Haskell) und wrap heißt return .

5voto

Dmitry Punkte 4722

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 Monoids zurückgeben. Z.B. Beliebige Zahl + Beliebige Zahl = Eine Zahl, keine Fehler.

Während division akzeptiert zwei fractionals, und gibt eine gebrochene, die Division durch Null als Unendlichkeit in haskell somewhy (die zufällig eine gebrochene somewhy) definiert...

In jedem Fall scheint es, dass Monaden nur ein Weg sind, um sicherzustellen, dass Ihre Kette von Operationen in einer vorhersehbaren Weise verhält, und eine Funktion, die behauptet, Num -> Num, mit einer anderen Funktion von Num->Num mit x aufgerufen komponiert nicht sagen, feuern die Raketen.

Wenn wir andererseits eine Funktion haben, die die Raketen abfeuert, können wir sie mit anderen Funktionen zusammenstellen, die ebenfalls die Raketen abfeuern, weil unsere Absicht klar ist - wir wollen die Raketen abfeuern -, aber sie wird nicht versuchen, "Hello World" aus irgendeinem seltsamen Grund zu drucken.

In Haskell ist main vom Typ IO (), oder IO [()], die Unterscheidung ist seltsam und ich werde sie nicht diskutieren, aber hier ist, was ich denke, was 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 durch IO. Ich kann also IO-Operationen in main aneinanderreihen, um - IO zu machen, sonst nichts.

Wenn ich versuche, etwas zu tun, das nicht "IO" zurückgibt, beschwert sich das Programm, dass die Kette nicht fließt, oder grundsätzlich "Wie bezieht sich dies auf das, was wir versuchen zu tun - eine IO-Aktion", es scheint den Programmierer zu zwingen, ihren Gedankengang beizubehalten, ohne abzuschweifen und über das Abfeuern der Raketen nachzudenken, während er Algorithmen zum Sortieren erstellt - was nicht fließt.

Im Grunde genommen scheinen Monaden ein Hinweis an den Compiler zu sein, der besagt: "Hey, du kennst doch diese Funktion, die eine Zahl zurückgibt, sie funktioniert nicht immer, sie kann manchmal eine Zahl ergeben und manchmal gar nichts, behalte das einfach im Hinterkopf". Wenn man das weiß, kann die monadische Aktion, wenn man versucht, eine monadische Aktion zu behaupten, als Kompilierzeit-Ausnahme fungieren, die sagt: "Hey, das ist nicht wirklich eine Zahl, das KANN eine Zahl sein, aber du kannst das nicht annehmen, tu etwas, um sicherzustellen, dass der Fluss akzeptabel ist", was unvorhersehbares Programmverhalten verhindert - bis zu einem gewissen Grad.

Es scheint, dass es bei Monaden weder um Reinheit noch um Kontrolle geht, sondern um die Aufrechterhaltung der Identität einer Kategorie, bei der das gesamte Verhalten vorhersehbar und definiert ist oder nicht kompiliert werden kann. Man kann nicht nichts tun, wenn von einem erwartet wird, dass man etwas tut, und man kann nicht etwas tun, wenn von einem erwartet wird, dass man nichts tut (sichtbar).

Der wichtigste Grund, der mir für Monaden einfällt, ist - schauen Sie sich prozeduralen/OOP-Code an, und Sie werden feststellen, dass Sie nicht wissen, wo das Programm beginnt oder endet, alles, was Sie sehen, ist eine Menge Sprünge und eine Menge Mathematik, Magie und Raketen. Sie werden nicht in der Lage sein, das Programm zu pflegen, und wenn Sie es können, werden Sie viel Zeit damit verbringen, das ganze Programm zu verstehen, bevor Sie auch nur einen Teil davon verstehen können, weil Modularität in diesem Zusammenhang auf voneinander abhängigen "Abschnitten" von Code basiert, bei denen der Code so optimiert ist, dass er so zusammenhängend wie möglich ist, um Effizienz/Zusammenhang zu versprechen. Monaden sind sehr konkret und per Definition gut definiert und stellen sicher, dass der Programmfluss analysiert werden kann und schwer zu analysierende Teile isoliert werden können - da sie selbst Monaden sind. Eine Monade scheint eine "verständliche Einheit zu sein, die bei vollständigem Verständnis vorhersehbar ist" -- Wenn man die Monade "Maybe" versteht, gibt es keine Möglichkeit, dass sie irgendetwas anderes tut, als "Maybe" zu sein, was trivial erscheint, aber in den meisten nicht monadischen Codes kann eine einfache Funktion "helloworld" die Raketen abfeuern, nichts tun, oder das Universum zerstören oder sogar die Zeit verzerren -- wir haben keine Ahnung und keine Garantien, dass ES IST, WAS ES IST. Eine Monade garantiert, dass ES IST, WAS ES IST, was sehr mächtig ist.

Alle Dinge in der "realen Welt" scheinen Monaden zu sein, in dem Sinne, dass sie an bestimmte beobachtbare Gesetze gebunden sind, die eine Verwechslung verhindern. Das bedeutet nicht, dass wir alle Operationen dieses Objekts nachahmen müssen, um Klassen zu bilden, stattdessen können wir einfach sagen: "Ein Quadrat ist ein Quadrat", nichts anderes als ein Quadrat, nicht einmal ein Rechteck oder ein Kreis, und "ein Quadrat hat eine Fläche, die der Länge einer seiner vorhandenen Dimensionen multipliziert mit sich selbst entspricht. Egal, was für ein Quadrat man hat, wenn es ein Quadrat im 2D-Raum ist, kann seine Fläche absolut nichts anderes sein als seine Länge zum Quadrat, das ist fast trivial zu beweisen. Das ist sehr mächtig, denn wir müssen keine Behauptungen aufstellen, um sicher zu gehen, dass unsere Welt so ist, wie sie ist, sondern wir nutzen einfach die Implikationen der Realität, um zu verhindern, dass unsere Programme aus der Bahn geraten.

Im ziemlich viel garantiert, um falsch zu sein, aber ich denke, dass dieses jemand heraus dort helfen könnte, also hoffentlich hilft es jemand.

5voto

samthebest Punkte 29529

Im Kontext von Scala ist die folgende Definition die einfachste. Grundsätzlich ist flatMap (oder bind) "assoziativ" und es existiert 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 Streng genommen ist die Definition eines Monade in der funktionalen Programmierung ist nicht dasselbe wie die Definition eines Monade in der Kategorientheorie die in Umdrehungen von map y flatten . Allerdings sind sie bei bestimmten Zuordnungen gleichwertig. Diese Präsentation ist sehr gut: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

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