Wir denken uns die zur Laufzeit bewegten Daten gesammelt in zwei Listen: einerseits die Liste der Fragen (bzw. Aufträge), die das Programm (bei seiner Ausführung) an das System stellt (erteilt), andererseits die Liste der Antworten, die das System dem Programm sagt.
Das Nutzer-Programm ist damit eine Funktion
f :: [ Antwort ] -> [ Frage ]Die Reihenfolge ist kein Tippfehler! Aus der Liste der Antworten berechnen wir die Liste der Fragen. Beispiel
f0 ( xs : rest ) = ( "getArgs" ) : f1 rest f1 ( h : rest ) = ( "hOpenFile " ++ head xs ) : f2 rest f2 ..Das System führt dann im Prinzip folgendes Programm aus
let aufgabe = f0 antworten in führe aufgaben der Reihe nach ausBeide Listen sind (potentiell) unendlich lang, das stört aber nicht, da wir lazy evaluation haben. Was hingegen stört, wäre
f0 ( x : rest ) = if x == 5 then ( ... ) else ( ... )das wird nicht gut gehen, denn die erste Antwort wird hier schon erwartet, bevor überhaupt eine Frage gestellt wurde.
Auf dieser expliziten Ebene fand tatsächlich in frühen Haskell-Versionen die Ein/Ausgabe statt. Das war natürlich erstens umständlich zu schreiben, und zweitens fehleranfällig (siehe voriges Beispiel).
Heutzutage steckt dieses Stream-Modell immer noch im Hintergrund (das geht auch gar nicht anders), aber wir haben
data IO a = IO ( [Antwort] -> ( a, [Frage] ) )und exportieren nicht die beiden Streams, sondern nur solche Werte IO a, die sinnvollen Operationen mit den Streams entsprechen.
Nun könnte uns die Haskell-Implementierungs-Geschichte egal sein, aber wir benutzen die gleiche Idee im MIDI-Sequencer (nächste Woche).