Ist die E / A-Monade technisch falsch?

12

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 awird 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 conditionist False, sollte die Aktion actionniemals 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 worldeinen 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?

Lasse
quelle
Willkommen in der Informatik ! Ihre Frage ist Wegthema hier: Wir beschäftigen uns mit Computer - Wissenschaft Fragen, keine Fragen der Programmierung (siehe unsere FAQ ). Ihre Frage ist möglicherweise zum Thema Stack Overflow (Stapelüberlauf) gerichtet .
Dkaeae
2
Meiner Meinung nach handelt es sich um eine Informatikfrage, da es sich um die theoretische Semantik von Haskell handelt, nicht um eine praktische Programmierfrage.
Lasse
4
Ich bin nicht sehr vertraut mit der Theorie der Programmiersprache, aber ich denke, diese Frage ist hier zum Thema. Es könnte hilfreich sein, wenn Sie klären, was "falsch" hier bedeutet. Welche Eigenschaft hat die IO-Monade Ihrer Meinung nach, die sie nicht haben sollte?
Diskrete Eidechse
1
Dieses Programm ist nicht gut geschrieben. Ich bin mir nicht sicher, was du eigentlich schreiben wolltest. Die Definition von whenist typisierbar, hat aber nicht den Typ, den Sie deklarieren, und ich sehe nicht, was diesen bestimmten Code interessant macht.
Gilles 'SO- hör auf böse zu sein'
2
Dieses Programm wurde wörtlich von der Haskell-Wiki-Seite übernommen, die direkt oben verlinkt ist. Es tippt ja nicht. Dies liegt daran, dass es unter der Annahme geschrieben wird, dass IO adefiniert ist als RealWorld -> (a, RealWorld), um die Interna von IO lesbarer zu machen.
Lasse

Antworten:

12

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.

Derek Elkins verließ SE
quelle
Vielen Dank für Ihre aufschlussreiche Antwort, es hat wirklich geholfen. Ihre Implementierung von IO hat einige Zeit in Anspruch genommen, aber ich stimme zu, dass es intuitiver ist. Behaupten Sie, dass diese Implementierung nicht unter potenziellen Problemen mit der Bestellung von Nebenwirkungen leidet, wie dies bei der RealWorld-Implementierung der Fall ist? Ich kann keine Probleme sofort erkennen, aber es ist mir auch nicht klar, dass sie nicht existieren.
Lasse
Ein Kommentar: Es scheint, dass OpenFile "foo" (\r -> ...)sollte eigentlich sein Request (OpenFile "foo") (\r -> ...)?
Lasse
@Lasse Ja, es sollte mit gewesen sein Request. Um Ihre erste Frage zu beantworten, IOist 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. Im whenBeispiel wäre es egal, ob actionausgewertet 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.
Derek Elkins verließ SE
Ah ja das sehe ich. Dies ist eine wirklich clevere Definition. Zuerst dachte ich, dass alle Nebenwirkungen unbedingt auftreten müssen, wenn das gesamte Programm ausgeführt wurde, da Sie die Datenstruktur erst erstellen müssen, bevor Sie sie interpretieren können. Da eine Anforderung jedoch eine Fortsetzung enthält, müssen Sie nur die Daten der ersten Anforderung erstellen Request, um Nebenwirkungen zu erkennen. Nachfolgende Nebenwirkungen können bei der Bewertung der Fortsetzung erzeugt werden. Klug!
Lasse