Angenommen, eine Funktion hat Nebenwirkungen. Wenn wir alle Effekte, die es erzeugt, als Eingabe- und Ausgabeparameter verwenden, ist die Funktion für die Außenwelt rein.
Also für eine unreine Funktion
f' :: Int -> Int
Wir fügen der Betrachtung die RealWorld hinzu
f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.
dann f
ist wieder rein. Wir definieren einen parametrisierten Datentyp type IO a = RealWorld -> (a, RealWorld)
, sodass wir RealWorld nicht so oft eingeben müssen und nur schreiben können
f :: Int -> IO Int
Für den Programmierer ist die direkte Handhabung einer RealWorld zu gefährlich. Insbesondere wenn ein Programmierer einen Wert vom Typ RealWorld in die Hände bekommt, versucht er möglicherweise, ihn zu kopieren , was im Grunde unmöglich ist. (Denken Sie beispielsweise daran, das gesamte Dateisystem zu kopieren. Wo würden Sie es ablegen?) Daher umfasst unsere Definition von E / A auch die Zustände der ganzen Welt.
Zusammensetzung "unreiner" Funktionen
Diese unreinen Funktionen sind nutzlos, wenn wir sie nicht miteinander verketten können. Erwägen
getLine :: IO String ~ RealWorld -> (String, RealWorld)
getContents :: String -> IO String ~ String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO () ~ String -> RealWorld -> ((), RealWorld)
Wir wollen
- Holen Sie sich einen Dateinamen von der Konsole,
- Lesen Sie diese Datei und
- Drucken Sie den Inhalt dieser Datei auf die Konsole.
Wie würden wir es tun, wenn wir Zugang zu den Staaten der realen Welt hätten?
printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
(contents, world2) = (getContents filename) world1
in (putStrLn contents) world2 -- results in ((), world3)
Wir sehen hier ein Muster. Die Funktionen heißen folgendermaßen:
...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...
Wir könnten also einen Operator definieren ~~~
, um sie zu binden:
(~~~) :: (IO b) -> (b -> IO c) -> IO c
(~~~) :: (RealWorld -> (b, RealWorld))
-> (b -> RealWorld -> (c, RealWorld))
-> (RealWorld -> (c, RealWorld))
(f ~~~ g) worldX = let (resF, worldY) = f worldX
in g resF worldY
dann könnten wir einfach schreiben
printFile = getLine ~~~ getContents ~~~ putStrLn
ohne die reale Welt zu berühren.
"Verunreinigung"
Angenommen, wir möchten den Dateiinhalt auch in Großbuchstaben schreiben. Großbuchstaben sind eine reine Funktion
upperCase :: String -> String
Aber um es in die reale Welt zu schaffen, muss es eine zurückgeben IO String
. Es ist einfach, eine solche Funktion aufzuheben:
impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)
Dies kann verallgemeinert werden:
impurify :: a -> IO a
impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)
so dass impureUpperCase = impurify . upperCase
, und wir können schreiben
printUpperCaseFile =
getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn
(Hinweis: Normalerweise schreiben wir getLine ~~~ getContents ~~~ (putStrLn . upperCase)
)
Wir haben die ganze Zeit mit Monaden gearbeitet
Nun wollen wir sehen, was wir getan haben:
- Wir haben einen Operator definiert,
(~~~) :: IO b -> (b -> IO c) -> IO c
der zwei unreine Funktionen miteinander verkettet
- Wir haben eine Funktion definiert
impurify :: a -> IO a
, die einen reinen Wert in unrein umwandelt.
Jetzt machen wir die Identifizierung (>>=) = (~~~)
und return = impurify
, und sehen? Wir haben eine Monade.
Technischer Hinweis
Um sicherzustellen, dass es sich wirklich um eine Monade handelt, müssen noch einige Axiome überprüft werden:
return a >>= f = f a
impurify a = (\world -> (a, world))
(impurify a ~~~ f) worldX = let (resF, worldY) = (\world -> (a, world )) worldX
in f resF worldY
= let (resF, worldY) = (a, worldX)
in f resF worldY
= f a worldX
f >>= return = f
(f ~~~ impurify) worldX = let (resF, worldY) = f worldX
in impurify resF worldY
= let (resF, worldY) = f worldX
in (resF, worldY)
= f worldX
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
Links als Übung.