Die Lesermonade ist so komplex und scheint nutzlos zu sein. In einer imperativen Sprache wie Java oder C ++ gibt es kein gleichwertiges Konzept für die Lesermonade, wenn ich mich nicht irre.
Können Sie mir ein einfaches Beispiel geben und dies ein wenig klären?
haskell
monads
reader-monad
chipbk10
quelle
quelle
Antworten:
Hab keine Angst! Die Lesermonade ist eigentlich nicht so kompliziert und hat ein wirklich einfach zu bedienendes Dienstprogramm.
Es gibt zwei Möglichkeiten, sich einer Monade zu nähern: Wir können fragen
Vom ersten Ansatz an ist die Lesermonade ein abstrakter Typ
so dass
Wie nutzen wir das? Nun, die Lesermonade ist gut geeignet, um (implizite) Konfigurationsinformationen durch eine Berechnung zu leiten.
Jedes Mal, wenn Sie eine "Konstante" in einer Berechnung haben, die Sie an verschiedenen Punkten benötigen, aber wirklich möchten, dass Sie dieselbe Berechnung mit unterschiedlichen Werten durchführen können, sollten Sie eine Lesermonade verwenden.
Lesermonaden werden auch verwendet, um das zu tun, was die OO-Leute Abhängigkeitsinjektion nennen . Beispielsweise wird der Negamax- Algorithmus häufig (in hochoptimierten Formen) verwendet, um den Wert einer Position in einem Zwei-Spieler-Spiel zu berechnen. Dem Algorithmus selbst ist es jedoch egal, welches Spiel Sie spielen, außer dass Sie in der Lage sein müssen, die "nächsten" Positionen im Spiel zu bestimmen, und Sie müssen in der Lage sein zu erkennen, ob die aktuelle Position eine Siegposition ist.
Dies funktioniert dann mit jedem endlichen, deterministischen Zwei-Spieler-Spiel.
Dieses Muster ist auch für Dinge nützlich, bei denen es sich nicht wirklich um Abhängigkeitsinjektion handelt. Angenommen, Sie arbeiten im Finanzbereich, dann entwerfen Sie möglicherweise eine komplizierte Logik für die Preisgestaltung eines Vermögenswerts (z. B. ein Derivat), die alle gut und schön ist und auf stinkende Monaden verzichten kann. Dann ändern Sie Ihr Programm, um mit mehreren Währungen umzugehen. Sie müssen in der Lage sein, im laufenden Betrieb zwischen Währungen umzurechnen. Ihr erster Versuch besteht darin, eine Funktion der obersten Ebene zu definieren
Spotpreise zu bekommen. Sie können dieses Wörterbuch dann in Ihrem Code aufrufen ... aber warten Sie! Das wird nicht funktionieren! Das Währungswörterbuch ist unveränderlich und muss daher nicht nur für die Lebensdauer Ihres Programms, sondern ab dem Zeitpunkt seiner Kompilierung gleich sein ! Also, was machst du? Nun, eine Option wäre die Verwendung der Reader-Monade:
Der vielleicht klassischste Anwendungsfall ist die Implementierung von Dolmetschern. Bevor wir uns das ansehen, müssen wir jedoch eine andere Funktion einführen
Okay, Haskell und andere funktionale Sprachen basieren auf dem Lambda-Kalkül . Lambda-Kalkül hat eine Syntax, die aussieht
und wir wollen einen Evaluator für diese Sprache schreiben. Zu diesem Zweck müssen wir eine Umgebung im Auge behalten, bei der es sich um eine Liste von Bindungen handelt, die mit Begriffen verknüpft sind (tatsächlich handelt es sich um Abschlüsse, da wir statisches Scoping durchführen möchten).
Wenn wir fertig sind, sollten wir einen Wert (oder einen Fehler) herausholen:
Schreiben wir also den Dolmetscher:
Schließlich können wir es verwenden, indem wir eine triviale Umgebung übergeben:
Und das ist alles. Ein voll funktionsfähiger Interpreter für den Lambda-Kalkül.
Die andere Möglichkeit, darüber nachzudenken, besteht darin, zu fragen: Wie wird es implementiert? Die Antwort ist, dass die Lesermonade tatsächlich eine der einfachsten und elegantesten aller Monaden ist.
Reader ist nur ein ausgefallener Name für Funktionen! Wir haben bereits definiert
runReader
, was ist also mit den anderen Teilen der API? Nun, jederMonad
ist auch einFunctor
:Nun, um eine Monade zu bekommen:
das ist nicht so beängstigend.
ask
ist wirklich einfach:während
local
ist nicht so schlimm:Okay, die Lesermonade ist nur eine Funktion. Warum überhaupt Reader? Gute Frage. Eigentlich brauchst du es nicht!
Diese sind noch einfacher. Was mehr ist,
ask
ist nurid
undlocal
ist nur Funktionszusammensetzung mit der Reihenfolge der Funktionen umgeschaltet!quelle
Reader
also eine Funktion mit einer bestimmten Implementierung der Monadentypklasse? Es früher zu sagen hätte mir geholfen, ein bisschen weniger verwirrt zu sein. Zuerst habe ich es nicht verstanden. Auf halbem Weg dachte ich: "Oh, es ermöglicht Ihnen, etwas zurückzugeben, das Ihnen das gewünschte Ergebnis liefert, sobald Sie den fehlenden Wert angeben." Ich fand das nützlich, merkte aber plötzlich, dass eine Funktion genau das tut.local
Funktion bedarf jedoch einiger weiterer Erklärungen.(Reader f) >>= g = (g (f x))
?x
?Ich erinnere mich, wie ich verwirrt war, bis ich selbst entdeckte, dass es überall Varianten der Reader-Monade gibt . Wie habe ich es entdeckt? Weil ich immer wieder Code schrieb, der sich als kleine Variation herausstellte.
Zum Beispiel schrieb ich irgendwann einen Code, um mit historischen Werten umzugehen . Werte, die sich im Laufe der Zeit ändern. Ein sehr einfaches Modell hierfür sind Funktionen von Zeitpunkten bis zum Wert zu diesem Zeitpunkt:
Die
Applicative
Instanz bedeutet, dass wenn Sie habenemployees :: History Day [Person]
undcustomers :: History Day [Person]
Sie dies tun können:Das heißt,
Functor
undApplicative
erlauben Sie uns, regelmäßige, nicht historische Funktionen anzupassen, um mit Geschichten zu arbeiten.Die Monadeninstanz wird am intuitivsten unter Berücksichtigung der Funktion verstanden
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
. Eine Funktion des Typsa -> History t b
ist eine Funktion , die eine Kartena
zu einer Vorgeschichte vonb
Werten; Zum Beispiel könnten SiegetSupervisor :: Person -> History Day Supervisor
und habengetVP :: Supervisor -> History Day VP
. In der Monad-Instanz fürHistory
geht es also darum, Funktionen wie diese zu erstellen. Zum BeispielgetSupervisor >=> getVP :: Person -> History Day VP
ist die Funktion, die für jedenPerson
die Geschichte vonVP
s erhält, die sie hatten.Nun, diese
History
Monade ist eigentlich genau das gleiche wieReader
.History t a
ist wirklich das gleiche wieReader t a
(was das gleiche ist wiet -> a
).Ein weiteres Beispiel: Ich habe kürzlich in Haskell Prototypen für OLAP- Designs erstellt. Eine Idee hier ist die eines "Hyperwürfels", bei dem es sich um eine Abbildung von Schnittpunkten einer Reihe von Dimensionen auf Werte handelt. Jetzt geht das schon wieder los:
Eine übliche Vorgehensweise bei Hyperwürfeln besteht darin, Skalarfunktionen mit mehreren Stellen auf entsprechende Punkte eines Hyperwürfels anzuwenden. Dies können wir erreichen, indem wir eine
Applicative
Instanz definieren fürHypercube
:Ich habe gerade den obigen
History
Code kopiert und die Namen geändert. Wie Sie sehen,Hypercube
ist auch gerechtReader
.Es geht weiter und weiter. Zum Beispiel laufen Sprachdolmetscher auch darauf hinaus
Reader
, wenn Sie dieses Modell anwenden:Reader
ask
Reader
Ausführungsumgebung.local
Eine gute Analogie ist, dass a
Reader r a
eina
mit "Löchern" darstellt, die Sie daran hindern, zu wissen, wovona
wir sprechen. Sie können eine tatsächliche nur erhalten,a
wenn Sie eine an liefernr
, um die Löcher zu füllen. Es gibt Unmengen solcher Dinge. In den obigen Beispielen ist ein "Verlauf" ein Wert, der erst berechnet werden kann, wenn Sie eine Zeit angeben. Ein Hypercube ist ein Wert, der erst berechnet werden kann, wenn Sie eine Schnittmenge angeben, und ein Sprachausdruck ist ein Wert, der dies kann wird erst berechnet, wenn Sie die Werte der Variablen angeben. Es gibt Ihnen auch eine Intuition darüber, warumReader r a
das gleiche ist wier -> a
, weil eine solche Funktion auch intuitiv eina
fehlendes istr
.Das
Functor
undApplicative
und dieMonad
Instanzen vonReader
sind daher eine sehr nützliche Verallgemeinerung für Fälle, in denen Sie etwas der Art "eina
fehlendesr
" modellieren und es Ihnen ermöglichen, diese "unvollständigen" Objekte so zu behandeln, als wären sie vollständig.Noch ein anderer Weg , um die gleiche Sache zu sagen: a
Reader r a
ist etwas , das verbrauchtr
und produzierta
, und dieFunctor
,Applicative
undMonad
Instanzen sind Grundmuster für mit ArbeitsReader
s.Functor
= mache einReader
, das die Ausgabe eines anderen ändertReader
;Applicative
= Verbinde zweiReader
s mit demselben Eingang und kombiniere ihre Ausgänge;Monad
= Untersuche das Ergebnis von aReader
und verwende es, um ein anderes zu konstruierenReader
. Dielocal
undwithReader
Funktionen = machen eineReader
, die die Eingabe in eine andere ändertReader
.quelle
GeneralizedNewtypeDeriving
Erweiterung herzuleitenFunctor
,Applicative
,Monad
etc. für newtypes auf der Grundlage ihrer zugrunde liegenden Typen.In Java oder C ++ können Sie problemlos von überall auf jede Variable zugreifen. Probleme treten auf, wenn Ihr Code Multithreading wird.
In Haskell haben Sie nur zwei Möglichkeiten, den Wert von einer Funktion an eine andere zu übergeben:
fn1 -> fn2 -> fn3
, benötigt die Funktionfn2
möglicherweise keinen Parameter, von dem Siefn1
an übergebenfn3
.Die Reader-Monade übergibt nur die Daten, die Sie zwischen Funktionen teilen möchten. Funktionen können diese Daten lesen, aber nicht ändern. Das ist alles, was die Reader-Monade ausmacht. Na ja, fast alle. Es gibt auch eine Reihe von Funktionen wie
local
, aber zum ersten Mal können Sie nur bleibenasks
.quelle
do
Notation zu schreiben, was besser wäre, wenn man ihn in eine reine Funktion umgestalten würde.where
Klausel an die eine angehängt ist, wird sie dann als dritte Möglichkeit zum Übergeben von Variablen akzeptiert?