Das Erste, was man an der Fortsetzungsmonade erkennen muss, ist, dass sie im Grunde genommen überhaupt nichts tut . Das ist wahr!
Die Grundidee einer Fortsetzung im Allgemeinen ist, dass sie den Rest einer Berechnung darstellt . Angenommen, wir haben einen Ausdruck wie diesen : foo (bar x y) z. Extrahieren Sie nun nur den in Klammern bar x ygesetzten Teil. Dies ist Teil des Gesamtausdrucks, aber nicht nur eine Funktion, die wir anwenden können. Stattdessen müssen sie etwas , das wir eine Funktion anwenden zu . Wir können also in diesem Fall vom "Rest der Berechnung" sprechen \a -> foo a z, auf den wir uns beziehen können, bar x yum die vollständige Form zu rekonstruieren.
Nun kommt es vor, dass dieses Konzept des "Restes der Berechnung" nützlich ist, aber es ist umständlich, damit zu arbeiten, da es etwas außerhalb des von uns in Betracht gezogenen Unterausdrucks ist. Damit die Dinge besser funktionieren, können wir die Dinge auf den Kopf stellen: Extrahieren Sie den Unterausdruck, an dem wir interessiert sind, und wickeln Sie ihn in eine Funktion ein, die ein Argument enthält, das den Rest der Berechnung darstellt : \k -> k (bar x y).
Diese modifizierte Version bietet uns viel Flexibilität - sie extrahiert nicht nur einen Unterausdruck aus ihrem Kontext, sondern ermöglicht es uns auch , diesen äußeren Kontext innerhalb des Unterausdrucks selbst zu manipulieren . Wir können es uns als eine Art suspendierte Berechnung vorstellen , die uns explizite Kontrolle darüber gibt, was als nächstes passiert. Wie können wir das verallgemeinern? Nun, der Unterausdruck ist so gut wie unverändert. Ersetzen wir ihn also einfach durch einen Parameter für die Inside-Out-Funktion und geben uns \x k -> k x- mit anderen Worten, nichts weiter als eine umgekehrte Funktionsanwendung . Wir könnten genauso gut schreiben flip ($)oder ein bisschen exotische Fremdsprache hinzufügen und es als Operator definieren |>.
Nun wäre es einfach, wenn auch mühsam und schrecklich verschleiert, jedes Stück eines Ausdrucks in diese Form zu übersetzen. Zum Glück gibt es einen besseren Weg. Wenn wir als Haskell-Programmierer denken, dass das Erstellen einer Berechnung in einem Hintergrundkontext das nächste ist, was wir denken , ist dies eine Monade? Und in diesem Fall lautet die Antwort ja , ja, das ist es.
Um daraus eine Monade zu machen, beginnen wir mit zwei Grundbausteinen:
- Für eine Monade
msteht ein Wert vom Typ m afür den Zugriff auf einen Wert vom Typ aim Kontext der Monade.
- Der Kern unserer "suspendierten Berechnungen" ist die gespiegelte Funktionsanwendung.
Was bedeutet es, in adiesem Zusammenhang Zugang zu etwas Typischem zu haben ? Es bedeutet nur , dass, für einen Wert x :: ahaben wir angewandt flip ($)auf x, uns eine Funktion geben , die eine Funktion übernimmt , die ein Argument vom Typ nimmt a, und wendet diese Funktion x. Angenommen, wir haben eine angehaltene Berechnung, die einen Wert vom Typ enthält Bool. Welchen Typ gibt uns das?
> :t flip ($) True
flip ($) True :: (Bool -> b) -> b
Für suspendierte Berechnungen m afunktioniert der Typ also mit (a -> b) -> b... was vielleicht ein Höhepunkt ist, da wir die Signatur für bereits kannten Cont, aber mich vorerst humorisieren .
Interessant ist hier, dass eine Art "Umkehrung" auch für den Typ der Monade gilt: Cont b astellt eine Funktion dar, die eine Funktion übernimmt a -> bund auswertet b. Da eine Fortsetzung "die Zukunft" einer Berechnung darstellt, arepräsentiert der Typ in der Signatur in gewissem Sinne "die Vergangenheit".
Also, ersetzt (a -> b) -> bmit Cont b a, was ist der monadischen Typ für unsere Grundbaustein der Reverse - Funktion Anwendung? a -> (a -> b) -> bübersetzt zu a -> Cont b a... der gleichen Typensignatur wie returnund tatsächlich ist es genau das, was es ist.
Von hier an fällt alles ziemlich direkt aus den Typen heraus: Es gibt im Grunde keinen vernünftigen Weg, um >>=außer der tatsächlichen Implementierung zu implementieren. Aber was ist es eigentlich zu tun ?
An dieser Stelle kommen wir zurück zu dem, was ich sagte zu Beginn: die Fortsetzung Monade ist nicht wirklich tut so gut wie nichts. Etwas vom Typ Cont r aist trivial äquivalent zu etwas vom gerechten Typ a, indem einfach idals Argument für die angehaltene Berechnung angegeben wird. Dies könnte dazu führen, dass man sich fragt, ob, wenn Cont r aes sich um eine Monade handelt, die Bekehrung aber so trivial ist, nicht aallein auch eine Monade sein sollte. Das funktioniert natürlich nicht so wie es ist, da es keinen Typkonstruktor gibt, der als MonadInstanz definiert werden kann, aber sagen wir, wir fügen einen trivialen Wrapper hinzu, wie z data Id a = Id a. Dies ist in der Tat eine Monade, nämlich die Identitätsmonade.
Was macht >>=die Identitätsmonade? Die Typensignatur ist Id a -> (a -> Id b) -> Id bäquivalent zu a -> (a -> b) -> b, was wiederum nur eine einfache Funktionsanwendung ist. Nachdem wir festgestellt haben, dass dies Cont r atrivial äquivalent zu ist Id a, können wir daraus schließen, dass es sich auch in diesem Fall (>>=)nur um eine Funktionsanwendung handelt .
Natürlich Cont r aist es eine verrückte umgekehrte Welt, in der jeder Ziegenbart hat. Was also tatsächlich passiert, ist, die Dinge auf verwirrende Weise durcheinander zu bringen, um zwei suspendierte Berechnungen zu einer neuen suspendierten Berechnung zusammenzufassen, aber im Wesentlichen gibt es eigentlich nichts Ungewöhnliches auf! Funktionen auf Argumente anwenden, ho hum, ein weiterer Tag im Leben eines funktionierenden Programmierers.