Aus den Problemen, die viele Programmierer mit Dialogen hatten, wurde
das monadische I/O-Konzept entwickelt. [PJW93] Es basiert auf
einer Programmiermethode, die in geschickter Weise Daten implizit
weiterreicht, den Monaden. [Wad92][Wad90]
Eine Sequentialisierung kann in rein funktionalen Sprachen nur durch
Funktionkomposition adequat erreicht werden. Haben wir zwei I/O
Funktionen und
, die nacheinander ausgeführt werden sollen,
so ist dieses am einfachsten zu erreichen, wenn
einen Wert hat den
als Eingabeargument zur Evaluierung benötigt. Wenn dieser Wert
von
dann in irgendeiner Weise noch die Umgebung (Filesystem etc.)
darstellt, dann ist auch die referentielle Transparenz gewährleistet.
Diesem Gedanken liegt das monadische I/O zugrunde. Monaden stellen
zwei Operatoren zur Verfügung: bind ein Sequenzoperator,
der durch Komposition
realisiert wird, und unit einen Operator, der es erlaubt,
komplexe Datentypen in eine Monade zusammenzupacken, die dann implizit
weitergereicht werden. Es kann also gut die für I/O benötigte Umgebung
(die World) in einer Monade enthalten sein und weitergereicht
werden.
Betrachten wir die beiden Aspekte des monadischen I/O getrennt:
wort :: IO String wort = wortaux [] wortaux String -> IO String wortaux w = getcIO `bindIO` \a -> if ((a== eof) \/ (a==' ')) then unitIO (reverse w) else (unitIO (a:w)) `bindIO` wortaux
type IO a = World -> IORes a data IORes a = MkIORes a WorldDer entscheidene Trick ist also, daIO a eine Funktion ist, die für eine Umgebung ein Element von Typ a mit dieser Umgebung über den Konstruktor MKIORes verknüpft. Die monadischen Funktionen werden definiert als:
unitIO a w = MkIORes a w bindIO m k w = case (m w) of MkIORes a w' -> k a w'Der Typ World ist ein abstrakter Typ mit genau einer auf ihm definierten Funktion, der Funktion ccall. Diese Funktion ist das einzige Sprachkonstrukt, daHaskell zum Implementieren des monadischen I/O hinzugefügt wurde. Es erlaubt eine beliebige C-Funktion aufzurufen, welche dann die Kommunikation mit der Umgebung betreibt. Mit Hilfe dieser Funktion werden die I/O-Funktionen definiert. Betrachten wir hierzu ein Beispiel:
putcIO a =\w -> case a of MkChar a# -> case (ccall# putchar a# w) of MkIORes# n# w' -> MkIORes () w'