Dies ist eine vorgeschlagene "Interpretation" der IO
Monade. Wenn Sie diese "Interpretation" ernst nehmen wollen, müssen Sie "RealWorld" ernst nehmen. Es ist unerheblich, ob action world
eine spekulative Bewertung vorgenommen wird oder nicht, ob action
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 Universums world
. Wir verwenden nicht das neue Universum, das wir möglicherweise nebenbei spekulativ bewertet haben. Der Zustand des Universums ist world
.
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 GHC IO
. 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, dass RealWorld
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:
data IO a = Return a | Request OSRequest (OSResponse -> IO a)
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 handelt Return
, 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:
data OSRequest = OpenFile FilePath | PutStr String | ...
Ebenso OSResponse
könnte etwas sein wie:
data OSResponse = Errno Int | OpenSucceeded Handle | ...
(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 einer PutStr
Anfrage.) Dieses Modell IO
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 behandelt PutStr "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 ein IO 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 von OpenFile "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 haben Fork :: (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 die IOSpec
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 wurde World
, aber es wird kein State-Passing durchgeführt. JHC und UHC verwendeten im Grunde den gleichen Ansatz wie GHC.