Im haskell-Wiki gibt es das folgende Beispiel für die bedingte Verwendung der IO-Monade (siehe hier) .
when :: Bool -> IO () -> IO ()
when condition action world =
if condition
then action world
else ((), world)
Beachten Sie, dass in diesem Beispiel die Definition von so verstanden IO a
wird RealWorld -> (a, RealWorld)
, dass alles verständlicher wird.
Dieses Snippet führt bedingt eine Aktion in der E / A-Monade aus. Unter der Annahme, dass dies der Fall condition
ist False
, sollte die Aktion action
niemals ausgeführt werden. Mit fauler Semantik wäre dies in der Tat der Fall. Es wird jedoch darauf hingewiesen , dass Haskell technisch gesehen nicht streng ist. Dies bedeutet, dass der Compiler beispielsweise action world
einen anderen Thread vorab ausführen und diese Berechnung später verwerfen kann, wenn er feststellt, dass er sie nicht benötigt. Zu diesem Zeitpunkt sind die Nebenwirkungen jedoch bereits eingetreten.
Jetzt könnte man die E / A-Monade so implementieren, dass Nebenwirkungen erst dann übertragen werden, wenn das gesamte Programm abgeschlossen ist, und wir genau wissen, welche Nebenwirkungen ausgeführt werden sollten. Dies ist jedoch nicht der Fall, da es möglich ist, unendliche Programme in Haskell zu schreiben, die eindeutig Nebenwirkungen haben.
Bedeutet dies, dass die E / A-Monade technisch falsch ist, oder verhindert etwas anderes dies?
when
ist typisierbar, hat aber nicht den Typ, den Sie deklarieren, und ich sehe nicht, was diesen bestimmten Code interessant macht.IO a
definiert ist alsRealWorld -> (a, RealWorld)
, um die Interna von IO lesbarer zu machen.Antworten:
Dies ist eine vorgeschlagene "Interpretation" der
IO
Monade. Wenn Sie diese "Interpretation" ernst nehmen wollen, müssen Sie "RealWorld" ernst nehmen. Es ist unerheblich, obaction world
eine spekulative Bewertung vorgenommen wird oder nicht, obaction
keine Nebenwirkungen vorliegen. Gegebenenfalls werden die Auswirkungen behoben, indem ein neuer Zustand des Universums zurückgegeben wird, in dem diese Auswirkungen aufgetreten sind, z. B. wenn ein Netzwerkpaket gesendet wurde. Das Ergebnis der Funktion ist jedoch((),world)
und damit der neue Zustand des Universumsworld
. Wir verwenden nicht das neue Universum, das wir möglicherweise nebenbei spekulativ bewertet haben. Der Zustand des Universums istworld
.Es fällt Ihnen wahrscheinlich schwer, das ernst zu nehmen. Es gibt viele Möglichkeiten, die bestenfalls oberflächlich paradox und unsinnig sind. Die Parallelität ist in dieser Perspektive entweder nicht offensichtlich oder verrückt.
"Warte, warte", sagst du. "
RealWorld
ist nur ein" Token ". Es ist nicht wirklich der Zustand des gesamten Universums." Okay, dann erklärt diese "Interpretation" nichts. Als Implementierungsdetail ist dies jedoch das Modell von GHCIO
. 1 Dies bedeutet jedoch, dass wir magische "Funktionen" haben, die tatsächlich Nebenwirkungen haben, und dieses Modell gibt keinen Hinweis auf ihre Bedeutung. Und da diese Funktionen tatsächlich Nebenwirkungen haben, ist die Sorge, die Sie vorbringen, absolut zutreffend. GHC muss alles daran setzen , um sicherzustellen, dassRealWorld
diese speziellen Funktionen nicht so optimiert werden, dass sich das beabsichtigte Verhalten des Programms ändert.Persönlich (wie wahrscheinlich mittlerweile offensichtlich ist) halte ich dieses "weltweite" Modell für ein
IO
nutzloses und verwirrendes pädagogisches Instrument. (Ob es für die Implementierung nützlich ist, weiß ich nicht. Für GHC ist es meiner Meinung nach eher ein historisches Artefakt.)Ein alternativer Ansatz besteht darin
IO
, Anforderungen mit Antwortbehandlungsroutinen als Beschreibung anzuzeigen . Dafür gibt es verschiedene Möglichkeiten. Am einfachsten ist es wahrscheinlich, eine kostenlose Monadenkonstruktion zu verwenden. Insbesondere können wir Folgendes verwenden:Es gibt viele Möglichkeiten, dies zu verfeinern und etwas bessere Eigenschaften zu erzielen, aber dies ist bereits eine Verbesserung. Es bedarf keiner tiefen philosophischen Annahmen über die Natur der Realität, um sie zu verstehen. Es heißt nur, dass
IO
es sich entweder um ein triviales Programm handeltReturn
, das nur einen Wert zurückgibt, oder um eine Anforderung an das Betriebssystem mit einem Handler für die Antwort.OSRequest
kann so etwas sein wie:Ebenso
OSResponse
könnte etwas sein wie:(Eine der Verbesserungen , die gemacht werden können , ist , die Dinge zu machen mehr geben sicher , so dass Sie wissen , dass Sie nicht bekommen
OpenSucceeded
von einerPutStr
Anfrage.) Dieses ModellIO
als Anforderungen beschreiben , die durch ein System interpretiert bekommen (für die „echten“IO
Monade ist dies die Haskell-Laufzeit selbst), und dann ruft dieses System möglicherweise den Handler auf, den wir mit einer Antwort versehen haben. Dies gibt natürlich auch keinen Hinweis darauf, wie eine Anfrage behandeltPutStr "hello world"
werden soll, aber es gibt auch nicht vor. Es wird ausdrücklich darauf hingewiesen, dass dies an ein anderes System delegiert wird. Dieses Modell ist auch ziemlich genau. Alle Benutzerprogramme in modernen Betriebssystemen müssen Anforderungen an das Betriebssystem stellen, um etwas zu tun.Dieses Modell bietet die richtigen Intuitionen. Beispielsweise sehen viele Anfänger Dinge wie den
<-
Operator als "Auspacken"IO
oder haben (leider verstärkte) Ansichten, die einIO String
"Container" ist, der "String
s " enthält (und sie dann<-
herausholt). Diese Anforderungs-Antwort-Sicht macht diese Perspektive eindeutig falsch. Es gibt kein Dateihandle innerhalb vonOpenFile "foo" (\r -> ...)
. Eine übliche Analogie, um dies zu betonen, ist, dass in einem Kuchenrezept kein Kuchen enthalten ist (oder in diesem Fall wäre eine "Rechnung" besser).Dieses Modell funktioniert auch problemlos mit Parallelität. Wir können leicht einen Konstruktor für
OSRequest
like habenFork :: (OSResponse -> IO ()) -> OSRequest
und dann kann die Laufzeit die Anforderungen, die von diesem zusätzlichen Handler erzeugt werden, mit dem normalen Handler verschachteln, wie es ihm gefällt. Mit etwas Geschick können Sie diese (oder verwandte) Techniken verwenden, um Dinge wie Parallelität direkter zu modellieren, anstatt nur zu sagen "Wir stellen eine Anfrage an das Betriebssystem und Dinge geschehen". So funktioniert dieIOSpec
Bibliothek .1 Hugs verwendete eine auf Fortsetzungen basierende Implementierung,
IO
die in etwa der von mir beschriebenen ähnelt, allerdings mit undurchsichtigen Funktionen anstelle eines expliziten Datentyps. HBC verwendete auch eine Continuation-basierte Implementierung, die über dem alten Request-Response-Stream-basierten IO lag. NHC (und damit YHC) verwendete Thunks, dh,IO a = () -> a
obwohl die()
aufgerufen wurdeWorld
, aber es wird kein State-Passing durchgeführt. JHC und UHC verwendeten im Grunde den gleichen Ansatz wie GHC.quelle
OpenFile "foo" (\r -> ...)
sollte eigentlich seinRequest (OpenFile "foo") (\r -> ...)
?Request
. Um Ihre erste Frage zu beantworten,IO
ist dies für die Bewertungsreihenfolge (Modulo Bottoms) eindeutig unempfindlich, da es sich um einen inerten Wert handelt. Alle Nebenwirkungen (falls vorhanden) würden von der Sache verursacht, die diesen Wert interpretiert. Imwhen
Beispiel wäre es egal, obaction
ausgewertet wurde, da es sich nur um einen Wert handelt,Request (PutStr "foo") (...)
den wir dem Ding, das diese Anforderungen interpretiert, sowieso nicht geben. Es ist wie Quellcode; Es spielt keine Rolle, ob Sie es eifrig oder träge reduzieren, es passiert nichts, bis es einem Dolmetscher übergeben wird.Request
, um Nebenwirkungen zu erkennen. Nachfolgende Nebenwirkungen können bei der Bewertung der Fortsetzung erzeugt werden. Klug!