Supposons qu'une fonction ait des effets secondaires. Si nous prenons tous les effets qu'elle produit comme paramètres d'entrée et de sortie, alors la fonction est pure pour le monde extérieur.
Donc, pour une fonction impure
f' :: Int -> Int
nous ajoutons le RealWorld à la considération
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.
puis f
est de nouveau pur. Nous définissons un type de données paramétré type IO a = RealWorld -> (a, RealWorld)
, nous n'avons donc pas besoin de taper RealWorld autant de fois, et pouvons simplement écrire
f :: Int -> IO Int
Pour le programmeur, manipuler directement un RealWorld est trop dangereux - en particulier, si un programmeur met la main sur une valeur de type RealWorld, il peut essayer de la copier , ce qui est fondamentalement impossible. (Pensez à essayer de copier tout le système de fichiers, par exemple. Où le mettrez-vous?) Par conséquent, notre définition d'E / S encapsule également les états du monde entier.
Composition des fonctions "impures"
Ces fonctions impures sont inutiles si nous ne pouvons pas les enchaîner. Considérer
getLine :: IO String ~ RealWorld -> (String, RealWorld)
getContents :: String -> IO String ~ String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO () ~ String -> RealWorld -> ((), RealWorld)
Nous voulons
- obtenir un nom de fichier depuis la console,
- lire ce fichier, et
- imprimer le contenu de ce fichier sur la console.
Comment le ferions-nous si nous pouvions accéder aux états du monde réel?
printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
(contents, world2) = (getContents filename) world1
in (putStrLn contents) world2 -- results in ((), world3)
Nous voyons un modèle ici. Les fonctions sont appelées comme ceci:
...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...
Nous pourrions donc définir un opérateur ~~~
pour les lier:
(~~~) :: (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
alors nous pourrions simplement écrire
printFile = getLine ~~~ getContents ~~~ putStrLn
sans toucher au monde réel.
"Impurification"
Supposons maintenant que nous voulions également mettre le contenu du fichier en majuscules. Les majuscules sont une fonction pure
upperCase :: String -> String
Mais pour entrer dans le monde réel, il doit renvoyer un fichier IO String
. Il est facile de lever une telle fonction:
impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)
Cela peut être généralisé:
impurify :: a -> IO a
impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)
pour que impureUpperCase = impurify . upperCase
, et nous pouvons écrire
printUpperCaseFile =
getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn
(Remarque: normalement, nous écrivons getLine ~~~ getContents ~~~ (putStrLn . upperCase)
)
Nous avons toujours travaillé avec des monades
Voyons maintenant ce que nous avons fait:
- Nous avons défini un opérateur
(~~~) :: IO b -> (b -> IO c) -> IO c
qui enchaîne deux fonctions impures ensemble
- Nous avons défini une fonction
impurify :: a -> IO a
qui convertit une valeur pure en valeur impure.
Maintenant nous faisons l'identification (>>=) = (~~~)
et return = impurify
, et voyons? Nous avons une monade.
Note technique
Pour s'assurer que c'est vraiment une monade, il y a encore quelques axiomes qui doivent également être vérifiés:
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
Laissé comme exercice.