Die offensichtlichste Neuerung, die Menschen in Haskell bemerken, ist die Trennung zwischen der unreinen Welt, die sich mit der Kommunikation mit der Außenwelt befasst, und der reinen Welt der Berechnungen und Algorithmen. Eine häufige Anfängerfrage lautet: "Wie kann ich loswerden IO
, dh konvertieren IO a
in a
?" Der Weg dorthin besteht darin, Monaden (oder andere Abstraktionen) zu verwenden, um Code zu schreiben, der E / A- und Ketteneffekte ausführt. Dieser Code sammelt Daten aus der Außenwelt, erstellt ein Modell davon, führt einige Berechnungen durch, möglicherweise unter Verwendung von reinem Code, und gibt das Ergebnis aus.
Was das obige Modell betrifft, sehe ich nichts Schreckliches daran, GUIs in der IO
Monade zu manipulieren . Das größte Problem, das sich aus diesem Stil ergibt, ist, dass Module nicht mehr zusammensetzbar sind, dh ich verliere den größten Teil meines Wissens über die globale Ausführungsreihenfolge von Anweisungen in meinem Programm. Um es wiederherzustellen, muss ich ähnliche Überlegungen anstellen wie im gleichzeitigen, zwingenden GUI-Code. Für unreinen Nicht-GUI-Code ist die Ausführungsreihenfolge aufgrund der Definition des IO
Monadenoperators offensichtlich >==
(mindestens solange nur ein Thread vorhanden ist). Für reinen Code spielt es keine Rolle, außer in Eckfällen, um die Leistung zu steigern oder Auswertungen zu vermeiden, die dazu führen ⊥
.
Der größte philosophische Unterschied zwischen Konsole und grafischer E / A besteht darin, dass Programme, die erstere implementieren, normalerweise synchron geschrieben werden. Dies ist möglich, weil es (abgesehen von Signalen und anderen offenen Dateideskriptoren) nur eine Ereignisquelle gibt: den allgemein aufgerufenen Bytestream stdin
. GUIs sind jedoch von Natur aus asynchron und müssen auf Tastaturereignisse und Mausklicks reagieren.
Eine beliebte Philosophie, asynchrone E / A auf funktionale Weise auszuführen, heißt Functional Reactive Programming (FRP). Dank Bibliotheken wie ReactiveX und Frameworks wie Elm hat es in letzter Zeit in unreinen, nicht funktionierenden Sprachen viel Anklang gefunden. Kurz gesagt, es ist so, als würde man GUI-Elemente und andere Dinge (wie Dateien, Uhren, Alarme, Tastatur, Maus) als Ereignisquellen, sogenannte "Observables", anzeigen, die Ereignisströme ausgeben. Diese Ereignisse werden mit dem bekannten Operatoren wie map
, foldl
, zip
, filter
, concat
, join
, etc., um neue Streams zu erzeugen. Dies ist nützlich, da der Programmstatus selbst ab scanl . map reactToEvents $ zipN <eventStreams>
dem Programm gesehen werden kann, wobei dies N
der Anzahl der Observablen entspricht, die jemals vom Programm berücksichtigt wurden.
Durch die Arbeit mit FRP-Observablen kann die Kompositionsfähigkeit wiederhergestellt werden, da Ereignisse in einem Stream rechtzeitig geordnet werden. Der Grund dafür ist, dass die Ereignisstromabstraktion es ermöglicht, alle Observablen als Black Boxes anzuzeigen. Letztendlich gibt das Kombinieren von Ereignisströmen mit Operatoren bei der Ausführung eine lokale Reihenfolge zurück. Dies zwingt mich, viel ehrlicher darüber zu sein, auf welche Invarianten sich mein Programm tatsächlich stützt, ähnlich wie alle Funktionen in Haskell referenziell transparent sein müssen: Wenn ich Daten aus einem anderen Teil meines Programms abrufen möchte, muss ich explizit sein Anzeige deklarieren Sie einen geeigneten Typ für meine Funktionen. (Die E / A-Monade, eine domänenspezifische Sprache zum Schreiben von unreinem Code, umgeht dies effektiv.)