Was ist in C # eine Monade?


189

Heutzutage wird viel über Monaden gesprochen. Ich habe einige Artikel / Blog-Beiträge gelesen, kann aber mit ihren Beispielen nicht weit genug gehen, um das Konzept vollständig zu verstehen. Der Grund dafür ist, dass Monaden ein funktionales Sprachkonzept sind und die Beispiele daher in Sprachen vorliegen, mit denen ich nicht gearbeitet habe (da ich keine funktionale Sprache ausführlich verwendet habe). Ich kann die Syntax nicht tief genug verstehen, um den Artikeln vollständig zu folgen ... aber ich kann sagen, dass es dort etwas gibt, das es wert ist, verstanden zu werden.

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

Ist es aber doch 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 von dort aus gerne tun würde , aber nicht, weil der Sprache funktionale Programmierfunktionen fehlen. Das wäre fantastisch, weil es die Absicht und den Nutzen von Monaden vermitteln würde. Hier ist meine Frage: Was ist die beste Erklärung, die Sie einem C # 3-Entwickler für Monaden geben können?

Vielen Dank!

(EDIT: Übrigens, ich weiß, dass es bereits mindestens 3 "Was ist eine Monade?" - Fragen auf SO gibt. Ich habe jedoch das gleiche Problem mit ihnen ... daher wird diese Frage aufgrund des C # -Entwicklers imo benötigt Fokus. Danke.)


Bitte beachten Sie, dass es sich tatsächlich um einen C # 3.0-Entwickler handelt. Verwechseln Sie es nicht mit .NET 3.5. Ansonsten schöne Frage.
Razzie

4
Es sei darauf hingewiesen, dass LINQ-Abfrageausdrücke ein Beispiel für monadisches Verhalten in C # 3 sind.
Erik Forbes

1
Ich denke immer noch, dass es eine doppelte Frage ist. Eine der Antworten in stackoverflow.com/questions/2366/can-anyone-explain-monads verlinkt auf channel9vip.orcsweb.com/shows/Going+Deep/… , wo einer der Kommentare ein sehr schönes C # -Beispiel enthält . :)
Jalf

4
Dies ist jedoch nur ein Link von einer Antwort zu einer der SO-Fragen. Ich sehe Wert in einer Frage, die sich an C # -Entwickler richtet. Es ist etwas, das ich einen funktionierenden Programmierer fragen würde, der früher C # gemacht hat, wenn ich eines kenne, also scheint es vernünftig, es auf SO zu fragen. Aber ich respektiere auch Ihr Recht auf Ihre Meinung.
Charlie Flowers

1
Ist eine Antwort nicht alles, was Sie brauchen? ;) Mein Punkt ist nur, dass eine der anderen Fragen (und jetzt auch diese, also yay) eine C # -spezifische Antwort hatte (die eigentlich wirklich gut geschrieben zu sein scheint. Wahrscheinlich die beste Erklärung, die ich gesehen habe)
jalf

Antworten:


147

Das meiste, was Sie den ganzen Tag programmieren, besteht darin, einige Funktionen miteinander zu kombinieren, um daraus größere Funktionen zu erstellen. Normalerweise haben Sie nicht nur Funktionen in Ihrer Toolbox, sondern auch andere Dinge wie Operatoren, Variablenzuweisungen und dergleichen, aber im Allgemeinen kombiniert Ihr Programm viele "Berechnungen" zu größeren Berechnungen, die weiter miteinander kombiniert werden.

Eine Monade ist eine Möglichkeit, diese "Kombination von Berechnungen" durchzuführen.

Normalerweise ist Ihr grundlegendster "Operator", um zwei Berechnungen miteinander zu kombinieren , ;:

a; b

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

Eine andere Sache, die in objektorientierten Sprachen als Monade angesehen werden kann, ist die .. Oft findet man solche Dinge:

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

Das .bedeutet im Grunde "die Berechnung auf der linken Seite auswerten und dann die Methode auf der rechten Seite als Ergebnis davon aufrufen". Es ist eine andere Möglichkeit, Funktionen / Berechnungen miteinander zu kombinieren, etwas komplizierter als ;. Und das Konzept, Dinge miteinander zu verketten, .ist eine Monade, da auf diese Weise zwei Berechnungen zu einer neuen Berechnung kombiniert werden können.

Eine andere ziemlich verbreitete 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 zeigt einen Fehler an, aber es gibt keine echte Möglichkeit, diese Fehlerprüfung zu abstrahieren, selbst wenn Sie viele API-Aufrufe haben, die Sie auf diese Weise kombinieren müssen. Dies ist im Grunde nur eine weitere Monade, die die Funktionsaufrufe nach der Regel kombiniert: "Wenn die Funktion links -1 zurückgibt, geben Sie -1 selbst zurück, andernfalls rufen Sie die Funktion rechts auf". Wenn wir einen Operator hätten >>=, der dies tut, könnten wir einfach schreiben:

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

Dies würde die Lesbarkeit verbessern und dazu beitragen, unsere spezielle Art der Funktionskombination zu abstrahieren, sodass wir uns nicht immer wieder wiederholen müssen.

Und es gibt viel mehr 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 das Obige >>=erweitert werden, um "die Fehlerprüfung durchzuführen und dann die rechte Seite des Sockets aufzurufen, den wir als Eingabe erhalten haben", so dass wir nicht socketoft 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, wenn diese Funktion diese Eingabe benötigt und Sie sicherstellen möchten, dass die von Ihnen kombinierten Funktionen passen die Art, wie Sie versuchen, sie in Ihrer Monade zu kombinieren. Das Grundkonzept besteht jedoch nur darin, dass Sie verschiedene Arten der Kombination von Funktionen formalisieren.


28
Gute Antwort! Ich werde ein Zitat von Oliver Steele einbringen und versuchen, Monaden mit der Überladung von Operatoren à la C ++ oder C # in Beziehung zu setzen: Mit Monaden können Sie das ';' Operator.
Jörg W Mittag

6
@ JörgWMittag Ich habe dieses Zitat schon einmal gelesen, aber es klang nach übermäßig berauschendem Unsinn. Jetzt, wo ich Monaden verstehe und diese Erklärung lese, wie ';' ist eins, ich verstehe. Aber ich denke, es ist wirklich eine irrationale Aussage für die meisten imperativen Entwickler. ';' wird nicht mehr als // als Operator gesehen.
Jimmy Hoffa

2
Bist du sicher, dass du weißt, was eine Monade ist? Monaden sind keine "Funktion" oder Berechnung, es gibt Regeln für Monaden.
Luis

In Ihrem ;Beispiel: Welche Objekte / Datentypen werden zugeordnet ;? (Think ListMaps Tto List<T>) Wie werden ;Morphismen / Funktionen zwischen Objekten / Datentypen abgebildet? Was ist pure, join, bindfür ;?
Micha Wiedenmann

44

Es ist ein Jahr her, seit ich diese Frage gestellt habe. Nachdem ich es veröffentlicht hatte, beschäftigte ich mich ein paar Monate lang mit Haskell. Ich habe es sehr genossen, aber ich habe es beiseite gelegt, als ich bereit war, mich mit Monaden zu beschäftigen. Ich machte mich wieder an die Arbeit und konzentrierte mich auf die Technologien, die mein Projekt benötigte.

Und letzte Nacht bin ich gekommen und habe diese Antworten noch einmal gelesen. Am wichtigsten ist , dass ich das spezifische C # -Beispiel in den Textkommentaren des Brian Beckman-Videos , das oben erwähnt wurde , erneut lese . Es war so klar und aufschlussreich, dass ich beschlossen habe, es direkt hier zu posten.

Aufgrund dieses Kommentars habe ich nicht nur das Gefühl, genau zu verstehen , was Monaden sind. Mir ist klar, dass ich tatsächlich einige Dinge in C # geschrieben habe, die Monaden sind… oder zumindest sehr nahe beieinander, und mich bemühe, dieselben Probleme zu lösen.

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

Das ist ziemlich cool. Es ist allerdings etwas abstrakt. Ich kann mir vorstellen, dass Leute, die nicht wissen, welche Monaden bereits sind, aufgrund des Mangels an echten Beispielen verwirrt sind.

Lassen Sie mich versuchen, die Anforderungen zu erfüllen, und um ganz klar zu sein, mache ich ein Beispiel in C #, auch wenn es hässlich aussehen wird. Ich werde am Ende das entsprechende Haskell hinzufügen und Ihnen den coolen syntaktischen Haskell-Zucker zeigen, bei dem, IMO, Monaden wirklich nützlich werden.

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

Eine nützliche Sache, um in einer Monade zu bleiben, um Werte dieses Typs zu kombinieren, ist der Begriff des Versagens. Das heißt, wir möchten in der Lage sein, mehrere nullbare Werte zu betrachten und zurückzukehren null, sobald einer von ihnen null ist. Dies kann nützlich sein, wenn Sie beispielsweise viele Schlüssel in einem Wörterbuch oder Ähnlichem nachschlagen und am Ende alle Ergebnisse verarbeiten und irgendwie kombinieren möchten. Wenn sich jedoch einer der Schlüssel nicht im Wörterbuch befindet, Sie wollen nullfür die ganze Sache zurückkehren. Es wäre mühsam, jede Suche manuell überprüfen nullund zurückgeben zu müssen, damit wir diese Überprüfung im Bindungsoperator verbergen können (was eine Art Punkt für Monaden ist, wir verstecken die Buchhaltung im Bindungsoperator, was den Code einfacher macht verwenden, da wir die Details vergessen können).

Hier ist das Programm, das das Ganze motiviert (ich werde das Bindspäter definieren , dies soll Ihnen nur zeigen, warum es schön 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();
        }
    }

Ignorieren Sie jetzt für einen Moment, dass dies Nullablein C # bereits unterstützt wird (Sie können nullfähige Ints zusammenfügen und erhalten null, wenn beides null ist). Stellen wir uns vor, es gibt keine solche Funktion und es handelt sich nur um eine benutzerdefinierte Klasse ohne besondere Magie. Der Punkt ist, dass wir die BindFunktion verwenden können, um eine Variable an den Inhalt unseres NullableWerts zu binden und dann so zu tun, als ob nichts Seltsames vor sich geht, und sie wie normale Ints verwenden und sie einfach addieren können. Wir wickeln das Ergebnis in einem nullable am Ende, und das NULL festlegbaren entweder null sein (wenn eine der f, goder hkehrt null) oder es wird das Ergebnis des Summierens sein f, gundhzusammen. (Dies ist analog dazu, wie wir eine Zeile in einer Datenbank an eine Variable in LINQ binden und damit arbeiten können, sicher in dem Wissen, dass der BindOperator sicherstellen wird, dass der Variablen immer nur gültige Zeilenwerte übergeben werden).

Sie können damit spielen und jedes von f, ändern gund hnull zurückgeben, und Sie werden sehen, dass das Ganze null zurückgibt.

Daher muss der Bind-Operator diese Überprüfung für uns durchführen und die Rückgabe von Null retten, wenn er auf einen Null-Wert stößt, und ansonsten den Wert innerhalb der NullableStruktur an das Lambda weitergeben.

Hier ist der BindOperator:

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 genau wie im Video. Es benötigt eine M a ( Nullable<A>in diesem Fall in C # -Syntax) und eine Funktion von abis M b( Func<A, Nullable<B>>in C # -Syntax) und gibt eine M b ( Nullable<B>) zurück.

Der Code prüft einfach, ob die Null-Datei einen Wert enthält, extrahiert ihn in diesem Fall und übergibt ihn an die Funktion. Andernfalls wird nur Null zurückgegeben. Dies bedeutet, dass der BindOperator die gesamte Nullprüflogik für uns übernimmt. Wenn und nur wenn der Wert, den wir aufrufen, Bindnicht null ist, wird dieser Wert an die Lambda-Funktion "weitergegeben", andernfalls werden wir frühzeitig aussteigen und der gesamte Ausdruck ist null. Dies ermöglicht es den Code , dass wir die Monade Schreib mit ganz diesem Null-Kontrolle Verhalten frei zu sein, wir verwenden nur Bindund eine Variable auf den Wert innerhalb des monadischen Wertes gebunden ( fval, gvalund hvalin dem Beispiel - Code) und wir können sie sicher nutzen in dem Wissen, Binddas sich darum kümmert, sie auf Null zu prüfen, bevor sie weitergegeben werden.

Es gibt andere Beispiele für Dinge, die Sie mit einer Monade tun können. Beispielsweise können Sie den BindOperator veranlassen, sich um einen Eingabestrom von Zeichen zu kümmern und damit Parser-Kombinatoren zu schreiben. Jeder Parser-Kombinator kann dann Dinge wie Back-Tracking, Parser-Fehler usw. völlig ignorieren und einfach kleinere Parser miteinander kombinieren, als ob niemals etwas schief gehen würde, sicher in dem Wissen, dass eine clevere Implementierung Binddie gesamte Logik hinter dem aussortiert schwierige Teile. Später fügt vielleicht jemand der Monade eine Protokollierung hinzu, aber der Code, der die Monade verwendet, ändert sich nicht, da die gesamte Magie in der Definition des BindOperators geschieht und der Rest des Codes unverändert bleibt.

Schließlich ist hier die Implementierung des gleichen Codes in Haskell ( -- beginnt eine Kommentarzeile).

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

Wie Sie sehen können, sieht die schöne doNotation am Ende wie ein gerader Imperativcode aus. Und in der Tat ist dies beabsichtigt. Monaden können verwendet werden, um alle nützlichen Dinge in der imperativen Programmierung (veränderlichen Zustand, E / A usw.) zu kapseln und mit dieser netten imperativartigen Syntax zu verwenden, aber hinter den Vorhängen sind alles nur Monaden und eine clevere Implementierung des Bind-Operators! Das Coole ist, dass Sie Ihre eigenen Monaden implementieren können, indem Sie >>=und implementieren return. Und wenn Sie dies tun, können diese Monaden auch die doNotation verwenden, was bedeutet, dass Sie im Grunde genommen Ihre eigenen kleinen Sprachen schreiben können, indem Sie nur zwei Funktionen definieren!


3
Persönlich bevorzuge ich F # 's Version der Monade, aber in jedem Fall sind sie fantastisch.
ChaosPandion

3
Vielen Dank, dass Sie hierher zurückgekommen sind und Ihren Beitrag aktualisiert haben. Es sind Folgemaßnahmen wie diese, die Programmierern, die sich mit einem bestimmten Bereich befassen, wirklich helfen, zu verstehen, wie andere Programmierer diesen Bereich letztendlich betrachten, anstatt einfach "Wie mache ich x in y-Technologie?" Zu verlassen. Du da Mann!
Kappasims

Ich bin den gleichen Weg gegangen, den Sie im Grunde genommen haben, und bin am gleichen Ort angekommen, um Monaden zu verstehen. Dies ist die beste Erklärung für das Bindungsverhalten einer Monade, das ich je für einen imperativen Entwickler gesehen habe. Obwohl ich denke, dass Sie nicht alles an Monaden berühren, was oben von etw. Etwas näher erläutert wurde.
Jimmy Hoffa

@ Jimmy Hoffa - ohne Zweifel hast du recht. Ich denke, um sie wirklich tiefer zu verstehen, ist der beste Weg, sie viel zu benutzen und Erfahrungen zu sammeln . Ich hatte diese Gelegenheit noch nicht, aber ich hoffe es bald.
Charlie Flowers

Es scheint, dass Monade nur eine höhere Abstraktionsebene in Bezug auf die Programmierung ist, oder es ist nur eine kontinuierliche und nicht differenzierbare Funktionsdefinition in der Mathematik. In beiden Fällen handelt es sich nicht um ein neues Konzept, insbesondere in der Mathematik.
Liang

11

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, müssen Sie ausweichen: "Ok, ich weiß, dass Sie keine Nebenwirkungen haben für mich, aber können Sie bitte berechnen, was passieren würde, wenn Sie es tun würden? "

Es ist eine Art Betrug.

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

Der beste Weg, um einen Überblick darüber zu geben, wie jemand an imperative Programmierung gewöhnt ist, besteht darin, zu sagen, dass Sie sich in einem DSL befinden, in dem Operationen, die syntaktisch so aussehen, wie Sie es außerhalb der Monade gewohnt sind, stattdessen verwendet werden, um eine Funktion zu erstellen, 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, die später ausgewertet werden soll.


1
Wie im I Robot Buch? Wo der Wissenschaftler einen Computer auffordert, die Raumfahrt zu berechnen und bestimmte Regeln zu überspringen? :) :) :) :)
OscarRyz

3
Hmm, eine Monade kann zur verzögerten Verarbeitung und zur Kapselung von Nebenwirkungen verwendet werden. Dies war zwar die erste echte Anwendung in Haskell, aber tatsächlich ein weitaus allgemeineres Muster. Andere häufige Anwendungen sind Fehlerbehandlung und Statusverwaltung. Der syntaktische Zucker (do in Haskell, Computation Expressions in F #, Linq-Syntax in C #) ist nur das und grundlegend für Monaden als solche.
Mike Hadlow

@MikeHadlow: Die Monadeninstanzen für Fehlerbehandlung ( Maybeund Either e) und Statusverwaltung ( State s, ST s) erscheinen mir als besondere Instanzen von "Bitte berechnen Sie, was passieren würde, wenn Sie [Nebenwirkungen für mich] tun würden". Ein anderes Beispiel wäre Nichtdeterminismus ( []).
pyon

das ist genau richtig; mit einer (also zwei) Hinzufügung, dass es sich um ein E- DSL handelt, dh um eingebettetes DSL, da jeder "monadische" Wert ein gültiger Wert Ihrer "reinen" Sprache selbst ist und für eine möglicherweise unreine "Berechnung" steht. Darüber hinaus gibt es in Ihrer reinen Sprache ein monadisches "Bind" -Konstrukt, mit dem Sie reine Konstruktoren solcher Werte verketten können, wobei jedes mit dem Ergebnis seiner vorherigen Berechnung aufgerufen wird, wenn die gesamte kombinierte Berechnung "ausgeführt" wird. Dies bedeutet, dass wir in der Lage sind, auf zukünftige Ergebnisse (oder auf jeden Fall auf die separate "Ausführungs" -Zeitleiste) zu verzweigen .
Will Ness

Für einen Programmierer bedeutet dies jedoch, dass wir in der EDSL programmieren können, während wir sie mit den reinen Berechnungen unserer reinen Sprache mischen. Ein Stapel mehrschichtiger Sandwiches ist ein mehrschichtiges Sandwich. so einfach ist das
Will Ness

4

Ich bin mir sicher, dass andere Benutzer ausführlich posten werden, aber ich fand dieses Video bis zu einem gewissen Grad hilfreich, aber ich werde sagen, dass ich mit dem Konzept immer noch nicht so fließend bin, dass ich mit dem Lösen beginnen könnte (oder sollte) Probleme intuitiv mit Monaden.


1
Was ich noch hilfreicher fand, war der Kommentar mit einem C # -Beispiel unter dem Video.
Jalf

Ich weiß nicht, ob es hilfreicher ist, aber es hat die Ideen auf jeden Fall in die Praxis umgesetzt.
TheMissingLINQ

0

Sie können sich eine Monade als C # vorstellen interface, das Klassen implementieren müssen . Dies ist eine pragmatische Antwort, die alle kategorietheoretischen Berechnungen ignoriert, die dahinter stehen, warum Sie diese Deklarationen in Ihrer Benutzeroberfläche haben möchten, und alle Gründe ignoriert, warum Sie Monaden in einer Sprache haben möchten, die versucht, Nebenwirkungen zu vermeiden. Aber ich fand es ein guter Anfang für jemanden, der (C #) -Schnittstellen versteht.


Können Sie das näher erläutern? Was ist mit einer Schnittstelle, die sie mit Monaden in Verbindung bringt?
Joel Coehoorn

2
Ich denke, der Blog-Beitrag verwendet mehrere Absätze, die dieser Frage gewidmet sind.
hao

0

Siehe meine Antwort auf "Was ist eine Monade?"

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

Es setzt keine Kenntnisse der funktionalen Programmierung voraus und verwendet Pseudocode mit function(argument) := expressionSyntax mit möglichst einfachen Ausdrücken.

Dieses C # -Programm ist eine Implementierung der Pseudocode-Monade. (Als Referenz: MIst der Typkonstruktor, feedist die "Bind" -Operation und wrapist 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);
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.