Dies ist eine vorgeschlagene "Interpretation" der IOMonade. Wenn Sie diese "Interpretation" ernst nehmen wollen, müssen Sie "RealWorld" ernst nehmen. Es ist unerheblich, ob action worldeine spekulative Bewertung vorgenommen wird oder nicht, ob actionkeine 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. " RealWorldist 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 RealWorlddiese 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 IOnutzloses 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 IOes 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. OSRequestkann so etwas sein wie:
data OSRequest = OpenFile FilePath | PutStr String | ...
Ebenso OSResponsekö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 OpenSucceededvon einer PutStrAnfrage.) Dieses Modell IOals Anforderungen beschreiben , die durch ein System interpretiert bekommen (für die „echten“ IOMonade 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" IOoder haben (leider verstärkte) Ansichten, die ein IO String"Container" ist, der " Strings " 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 OSRequestlike haben Fork :: (OSResponse -> IO ()) -> OSRequestund 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 IOSpecBibliothek .
1 Hugs verwendete eine auf Fortsetzungen basierende Implementierung, IOdie 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 = () -> aobwohl die ()aufgerufen wurde World, aber es wird kein State-Passing durchgeführt. JHC und UHC verwendeten im Grunde den gleichen Ansatz wie GHC.