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 y
gesetzten 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 y
um 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
m
steht ein Wert vom Typ m a
für den Zugriff auf einen Wert vom Typ a
im Kontext der Monade.
- Der Kern unserer "suspendierten Berechnungen" ist die gespiegelte Funktionsanwendung.
Was bedeutet es, in a
diesem Zusammenhang Zugang zu etwas Typischem zu haben ? Es bedeutet nur , dass, für einen Wert x :: a
haben 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 a
funktioniert 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 a
stellt eine Funktion dar, die eine Funktion übernimmt a -> b
und auswertet b
. Da eine Fortsetzung "die Zukunft" einer Berechnung darstellt, a
repräsentiert der Typ in der Signatur in gewissem Sinne "die Vergangenheit".
Also, ersetzt (a -> b) -> b
mit 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 return
und 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 a
ist trivial äquivalent zu etwas vom gerechten Typ a
, indem einfach id
als Argument für die angehaltene Berechnung angegeben wird. Dies könnte dazu führen, dass man sich fragt, ob, wenn Cont r a
es sich um eine Monade handelt, die Bekehrung aber so trivial ist, nicht a
allein auch eine Monade sein sollte. Das funktioniert natürlich nicht so wie es ist, da es keinen Typkonstruktor gibt, der als Monad
Instanz 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 a
trivial ä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 a
ist 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.