webentwicklung-frage-antwort-db.com.de

Helfen Sie einem C # -Entwickler, zu verstehen: Was ist eine Monade?

In diesen Tagen wird viel über Monaden gesprochen. Ich habe einige Artikel/Blogbeiträge gelesen, aber ich kann mit ihren Beispielen nicht weit genug gehen, um das Konzept vollständig zu verstehen. Der Grund ist, dass Monaden ein funktionales Sprachkonzept sind, und die Beispiele sind daher in Sprachen, mit denen ich nicht gearbeitet habe (da ich bisher keine funktionale Sprache verwendet habe). Ich kann die Syntax nicht tief genug verstehen, um die Artikel vollständig zu verfolgen ... aber ich kann sagen, dass dort etwas zu verstehen ist, das es zu verstehen gilt.

Ich kenne C # jedoch ziemlich gut, einschließlich Lambda-Ausdrücken und anderen funktionalen Funktionen. Ich weiß, dass C # nur eine Teilmenge funktionaler Funktionen hat, und daher können Monaden möglicherweise nicht in C # ausgedrückt werden.

Sicher ist es jedoch möglich, das Konzept zu vermitteln. Zumindest hoffe ich das. Vielleicht können Sie ein C # -Beispiel als Grundlage präsentieren und dann beschreiben, was ein C # -Entwickler Wunsch von dort aus tun könnte, was aber nicht möglich ist, da der Sprache keine funktionalen Programmierfunktionen fehlen. Das wäre fantastisch, weil es die Absichten und Vorteile von Monaden vermitteln würde. Also hier meine Frage: Was ist die beste Erklärung, die Sie einem C # 3-Entwickler von Monaden geben können?

Vielen Dank!

(EDIT: Übrigens, ich weiß, dass es bereits mindestens 3 "Was ist eine Monade" -Frage zu SO gibt. Ich stehe jedoch vor dem gleichen Problem ... also ist diese Frage imo wegen des C # -Entwicklers notwendig danke. Danke

180
Charlie Flowers

Das meiste, was Sie den ganzen Tag beim Programmieren tun, ist die Kombination einiger Funktionen, um daraus größere Funktionen zu erstellen. Normalerweise verfügen Sie nicht nur über Funktionen in Ihrer Toolbox, sondern auch über andere Funktionen wie Operatoren, Variablenzuweisungen und dergleichen. In der Regel werden in Ihrem Programm viele "Berechnungen" zu größeren Berechnungen zusammengefasst, die weiter kombiniert werden.

Eine Monade ist eine Möglichkeit, dieses "Kombinieren von Berechnungen" auszuführen.

Normalerweise ist der einfachste Operator, der zwei Berechnungen zusammenfasst, ;:

a; b

Wenn Sie dies sagen, meinen Sie "zuerst a, dann b". Das Ergebnis a; b ist im Grunde wieder eine Berechnung, die mit mehr Material kombiniert werden kann ..__ Dies ist eine einfache Monade, mit der kleine Berechnungen mit größeren Berechnungen kombiniert werden können. Der ; sagt "mach das Ding links, dann mach das Ding rechts".

Eine andere Sache, die in objektorientierten Sprachen als Monade angesehen werden kann, ist der .. Oft finden Sie solche Dinge:

a.b().c().d()

Der . bedeutet im Grunde "die Berechnung auf der linken Seite auswerten und dann die Methode auf der rechten Seite des Ergebnisses aufrufen". Es ist eine andere Möglichkeit, Funktionen/Berechnungen miteinander zu kombinieren, etwas komplizierter als ;. Und das Konzept der Verkettung von Dingen mit . ist eine Monade, da es eine Möglichkeit ist, zwei Berechnungen zu einer neuen Berechnung zusammenzufassen.

Eine andere ziemlich häufige Monade, die keine spezielle Syntax hat, ist dieses Muster:

rv = socket.bind(address, port);
if (rv == -1)
  return -1;

rv = socket.connect(...);
if (rv == -1)
  return -1;

rv = socket.send(...);
if (rv == -1)
  return -1;

Ein Rückgabewert von -1 gibt einen Fehler an. Es gibt jedoch keine wirkliche Möglichkeit, diese Fehlerprüfung auszugleichen, selbst wenn Sie viele API-Aufrufe haben, die Sie auf diese Weise kombinieren müssen. Dies ist im Grunde nur eine andere Monade, die die Funktionsaufrufe mit der Regel kombiniert: "Wenn die Funktion auf der linken Seite -1 zurückgegeben hat, geben Sie selbst -1 zurück, andernfalls rufen Sie die Funktion auf der rechten Seite auf". Wenn wir einen Operator >>= hätten, der das getan hat, könnten wir einfach schreiben:

socket.bind(...) >>= socket.connect(...) >>= socket.send(...)

Es würde die Dinge lesbarer machen und helfen, unsere spezielle Art der Kombination von Funktionen herauszuarbeiten, so dass wir uns nicht immer wieder wiederholen müssen.

Und es gibt viele weitere Möglichkeiten, Funktionen/Berechnungen zu kombinieren, die als allgemeines Muster nützlich sind und in einer Monade abstrahiert werden können, so dass der Benutzer der Monade viel präziseren und klareren Code schreiben kann, da die gesamte Buchhaltung und Verwaltung von Die verwendeten Funktionen werden in der Monade ausgeführt.

Zum Beispiel könnte der obige >>= erweitert werden, um "die Fehlerprüfung durchzuführen und dann die rechte Seite des Sockets anzurufen, den wir als Eingabe erhalten haben", so dass wir socket nicht oft explizit angeben müssen:

new socket() >>= bind(...) >>= connect(...) >>= send(...);

Die formale Definition ist etwas komplizierter, da Sie sich Gedanken darüber machen müssen, wie Sie das Ergebnis einer Funktion als Eingabe für die nächste erhalten, ob diese Funktion diese Eingabe benötigt und Sie sicherstellen möchten, dass die von Ihnen kombinierten Funktionen ineinander passen die Art und Weise, wie Sie versuchen, sie in Ihrer Monade zu kombinieren. Das grundlegende Konzept ist jedoch nur, dass Sie verschiedene Möglichkeiten formalisieren, um Funktionen miteinander zu kombinieren.

144
sth

Es ist ein Jahr her, seit ich diese Frage gepostet habe. Nachdem ich es veröffentlicht hatte, habe ich mich ein paar Monate mit Haskell beschäftigt. Ich habe es enorm genossen, aber ich habe es beiseite gelegt, gerade als ich bereit war, mich in Monaden zu vertiefen. Ich ging wieder an die Arbeit und konzentrierte mich auf die Technologien, die für mein Projekt erforderlich waren.

Und letzte Nacht kam ich und las diese Antworten noch einmal. Am wichtigsten lese ich das spezifische C # -Beispiel in den Textkommentaren von das Brian Beckman-Video jemand Erwähnungen oben . Es war so klar und aufschlussreich, dass ich mich entschieden habe, es direkt hier zu posten.

Aufgrund dieses Kommentars habe ich nicht nur das Gefühl, dass ich genau verstehe, was Monaden sind ... Ich erkenne, dass ich tatsächlich einige Dinge in C # geschrieben habe, die Monaden sind ... oder zumindest sehr nahe und bestrebt sind lösen die gleichen probleme.

Also hier ist der Kommentar - dies ist alles ein direktes Zitat aus dem Kommentar hier von sylvan :

Das ist ziemlich cool. Es ist zwar etwas abstrakt. Ich kann mir Menschen vorstellen. Wer nicht weiß, welche Monaden bereits durch den Mangel an .__ verwirrt sind. echte Beispiele.

Lassen Sie mich versuchen, zu entsprechen, und um wirklich klar zu sein, mache ich ein Beispiel in C #, obwohl es hässlich aussehen wird. Ich werde das Äquivalent .__ hinzufügen. Haskell am Ende und zeige dir den coolen Haskell-Zucker, der IMO werden Monaden wirklich nützlich.

Okay, eine der einfachsten Monaden wird in .__ "Vielleicht Monade" genannt. Haskell In C # heißt der Vielleicht-Typ Nullable<T>. Es ist im Grunde eine winzige Klasse, die nur das Konzept eines Wertes einschließt, der .__ ist. entweder gültig und hat einen Wert oder ist "null" und hat keinen Wert.

Eine nützliche Sache, um innerhalb einer Monade zu bleiben, um Werte von diesem zu kombinieren Typ ist der Begriff des Scheiterns. Das heißt wir wollen .__ sehen können. mehrere nullfähige Werte und geben null zurück, sobald einer von ihnen ist Null. Dies kann nützlich sein, wenn Sie beispielsweise eine Menge von .__ nachschlagen. Schlüssel in einem Wörterbuch oder etwas und am Ende möchten Sie bearbeiten alle Ergebnisse und kombinieren Sie sie irgendwie, aber wenn einer der Schlüssel sind nicht im Wörterbuch, möchten Sie null für das gesamte .__ zurückgeben. Ding. Es wäre mühsam, jede Suche manuell nach .__ zu durchsuchen. null und return, damit wir diese Überprüfung im Bind ausblenden können operator (was sozusagen der Punkt der Monaden ist, verbergen wir die Buchführung im Bind-Operator, wodurch der Code einfacher zu verwenden ist, da wir die Details vergessen können ).

Hier ist das Programm, das die ganze Sache motiviert (ich werde die Bind später definieren, dies soll Ihnen nur zeigen, warum es Nizza ist).

 class Program
    {
        static Nullable<int> f(){ return 4; }        
        static Nullable<int> g(){ return 7; }
        static Nullable<int> h(){ return 9; }


        static void Main(string[] args)
        {
            Nullable<int> z = 
                        f().Bind( fval => 
                            g().Bind( gval => 
                                h().Bind( hval =>
                                    new Nullable<int>( fval + gval + hval ))));

            Console.WriteLine(
                    "z = {0}", z.HasValue ? z.Value.ToString() : "null" );
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }

Ignoriere jetzt für einen Moment, dass es bereits Unterstützung dafür gibt for Nullable in C # (Sie können nullfähige Ints zusammen hinzufügen und erhalten null, wenn beide null sind). Lassen Sie uns so tun, als gäbe es keine solche Funktion, und es ist nur eine benutzerdefinierte Klasse ohne besondere Magie. Der Punkt ist dass wir die Funktion Bind verwenden können, um eine Variable an den Inhalt zu binden von unserem Nullable Wert und dann so tun, als gäbe es nichts seltsames weiter, und verwenden Sie sie wie normale Ints und fügen Sie sie einfach zusammen. Wir Umfassen Sie das Ergebnis am Ende in eine Nullwerte, und diese Nullwerte werden entweder null sein (falls einer von f, g oder h null ergibt) oder es wird .__ sein. das Ergebnis der Addition von f, g und h zusammen. (Dies ist analog..... der Methode, wie wir eine Zeile in einer Datenbank an eine Variable in LINQ binden können, und tun.). Sicher damit, dass der Operator Bind sicherstellt dass die Variable immer nur gültige Zeilen ( Werte) übergeben wird.

Sie können damit spielen und f, g und h ändern, um zurückzukehren null und Sie werden sehen, dass das Ganze null zurückgibt.

Der Bindungsoperator muss also eindeutig diese Überprüfung für uns durchführen und die Kaution büßen out gibt null zurück, wenn es auf einen Nullwert trifft, und übergeben Sie andernfalls entlang des Wertes innerhalb der Nullable Struktur in das Lambda.

Hier ist der Operator Bind:

public static Nullable<B> Bind<A,B>( this Nullable<A> a, Func<A,Nullable<B>> f ) 
    where B : struct 
    where A : struct
{
    return a.HasValue ? f(a.Value) : null;
}

Die Typen hier sind wie im Video. Es braucht einen M a (Nullable<A> in C # -Syntax für diesen Fall) und eine Funktion von a bis M b (Func<A, Nullable<B>> in der C # -Syntax) und gibt einen M b.__ zurück. (Nullable<B>).Der Code prüft einfach, ob die Nullwerte einen Wert enthält, und wenn ja, __. extrahiert es und gibt es an die Funktion weiter, andernfalls wird nur __ zurückgegeben. Null. Dies bedeutet, dass der Operator Bind alle .__-Befehle verarbeitet. Null-Prüflogik für uns. Wenn und nur wenn der Wert, den wir .__ nennen. Bind on ist nicht null, dann wird dieser Wert an die .__ übergeben. Lambda-Funktion, ansonsten werden wir frühzeitig aufgestanden und der ganze Ausdruck lautet Null. Dadurch kann der Code, den wir mit der Monade schreiben, .__ sein. völlig frei von diesem null-überprüfungsverhalten, verwenden wir einfach Bind und Holen Sie sich eine Variable, die an den Wert innerhalb des monadischen Werts gebunden ist (--variable -, fval und gval im Beispielcode), und wir können sie sicher in der .__ verwenden. das Wissen, dass --variable - vor .__ für die Überprüfung auf null sorgen wird. sie weitergeben.

Es gibt andere Beispiele für Dinge, die Sie mit einer Monade machen können. Zum Beispiel: Sie können den Operator hval so einrichten, dass er sich um einen Eingabestrom kümmert von Zeichen, und verwenden Sie es, um Parser-Kombinatoren zu schreiben. Jeder Parser Der Kombinator kann dann Dinge wie .__ nicht wahrnehmen. Back-Tracking, Parser-Fehler usw., und kombinieren Sie einfach kleinere Parser zusammen, als ob die Dinge nie schief gehen würden, sicher im Wissen, dass Eine clevere Implementierung von Bind löst die gesamte Logik hinter dem .__ aus. schwierige Bits. Später fügt vielleicht jemand der Monade Logging hinzu, aber der Code, der die Monade verwendet, ändert sich nicht, weil die ganze Magie geschieht in der Definition des Bind Operators, der Rest des Codes ist unverändert.

Zum Schluss noch die Implementierung desselben Codes in Haskell (--CODE - Beginnt eine Kommentarzeile).

--.

Wie Sie sehen können, sieht die Nice-Variable -Notation am Ende aus wie 
 gerader imperativer Code. Und dies ist in der Tat beabsichtigt. Monaden können .__ sein. verwendet, um alle nützlichen Dinge in der imperativen Programmierung einzukapseln 
 (veränderbarer Zustand, IO usw.) und mit diesem Nice-Imperativ-ähnlichen .__ verwendet. Syntax, aber hinter den Vorhängen sind es nur Monaden und eine clevere 
 Implementierung des Bind-Operators! Die coole Sache ist, dass Sie 
 Implementiere deine eigenen Monaden durch die Implementierung von -- Here's the data type, it's either nothing, or "Just" a value
-- this is in the standard library
data Maybe a = Nothing | Just a

-- The bind operator for Nothing
Nothing >>= f = Nothing
-- The bind operator for Just x
Just x >>= f = f x

-- the "unit", called "return"
return = Just

-- The sample code using the lambda syntax
-- that Brian showed
z = f >>= ( \fval ->
     g >>= ( \gval ->  
     h >>= ( \hval -> return (fval+gval+hval ) ) ) )

-- The following is exactly the same as the three lines above
z2 = do 
   fval <- f
   gval <- g
   hval <- h
   return (fval+gval+hval)
 und Bind. Und wenn
 Wenn Sie dies tun, können diese Monaden auch die Bind -Notation verwenden, 
 Das bedeutet, dass Sie im Prinzip nur mit .__ Ihre eigenen kleinen Sprachen schreiben können. zwei Funktionen definieren!

As you can see the Nice do notation at the end makes it look like straight imperative code. And indeed this is by design. Monads can be used to encapsulate all the useful stuff in imperative programming (mutable state, IO etc.) and used using this Nice imperative-like syntax, but behind the curtains, it's all just monads and a clever implementation of the bind operator! The cool thing is that you can implement your own monads by implementing >>= and return. And if you do so those monads will also be able to use the do notation, which means you can basically write your own little languages by just defining two functions!

42
Charlie Flowers

Eine Monade ist im Wesentlichen eine verzögerte Verarbeitung. Wenn Sie versuchen, Code mit Nebenwirkungen (z. B. E/A) in einer Sprache zu schreiben, die diese nicht zulässt und nur reine Berechnungen zulässt, lautet ein Ausweichmanöver: "Ok, ich weiß, Sie werden keine Nebenwirkungen verursachen für mich, aber können Sie bitte berechnen, was passieren würde, wenn Sie es taten? "

Es ist eine Art Betrug.

Diese Erklärung wird Ihnen helfen, die Gesamtabsicht von Monaden zu verstehen, aber der Teufel steckt im Detail. Wie genau berechnen Sie die Konsequenzen? Manchmal ist es nicht schön.

Der beste Weg, um einen Überblick über die Art und Weise zu geben, wie ein Benutzer die imperative Programmierung gewohnt ist, besteht darin, zu sagen, dass Sie in eine DSL versetzt werden, in der Operationen, die syntaktisch so aussehen, wie Sie es außerhalb der Monade gewohnt sind, stattdessen zum Erstellen einer Funktion verwendet werden, die dies tun würde was Sie wollen, wenn Sie (zum Beispiel) in eine Ausgabedatei schreiben könnten. Fast (aber nicht wirklich) so, als würden Sie Code in einer Zeichenfolge erstellen, um ihn später auszuwerten.

11
MarkusQ

Ich bin sicher, dass andere Benutzer ausführlich posten werden, aber ich habe dieses Video in einem gewissen Umfang hilfreich gefunden, aber ich werde sagen, dass ich immer noch nicht so fließend mit dem Konzept bin, dass ich es konnte (oder sollte) beginnen, Probleme intuitiv mit Monaden zu lösen.

4
TheMissingLINQ

Siehe meine Antwort zu "Was ist eine Monade?"

Es beginnt mit einem motivierenden Beispiel, geht das Beispiel durch, leitet ein Beispiel einer Monade ab und definiert formal "Monad".

Es setzt keine Kenntnisse der funktionalen Programmierung voraus und verwendet Pseudocode mit function(argument) := expression-Syntax mit den einfachsten möglichen Ausdrücken.

Dieses C # -Programm ist eine Implementierung der Pseudocode-Monade. (Zur Referenz: M ist der Typkonstruktor, feed ist die "bind" -Operation und wrap ist die "return" -Operation.)

using System.IO;
using System;

class Program
{
    public class M<A>
    {
        public A val;
        public string messages;
    }

    public static M<B> feed<A, B>(Func<A, M<B>> f, M<A> x)
    {
        M<B> m = f(x.val);
        m.messages = x.messages + m.messages;
        return m;
    }

    public static M<A> wrap<A>(A x)
    {
        M<A> m = new M<A>();
        m.val = x;
        m.messages = "";
        return m;
    }

    public class T {};
    public class U {};
    public class V {};

    public static M<U> g(V x)
    {
        M<U> m = new M<U>();
        m.messages = "called g.\n";
        return m;
    }

    public static M<T> f(U x)
    {
        M<T> m = new M<T>();
        m.messages = "called f.\n";
        return m;
    }

    static void Main()
    {
        V x = new V();
        M<T> m = feed<U, T>(f, feed(g, wrap<V>(x)));
        Console.Write(m.messages);
    }
}
0
Jordan

Sie können sich eine Monade als eine C # interface vorstellen, die Klassen implementieren müssen. Dies ist eine pragmatische Antwort, die alle theoretischen Kategorien der Kategorie ignoriert, weshalb Sie diese Deklarationen in Ihrer Benutzeroberfläche verwenden möchten. Sie ignorieren alle Gründe, warum Sie Monaden in einer Sprache haben möchten, in der Nebenwirkungen vermieden werden. Aber ich fand es ein guter Anfang als jemand, der (C #) -Interfaces versteht.

0
hao