Supposons qu'une fonction a des effets secondaires. Si nous prenons tous les effets à produire que l'entrée et les paramètres de sortie, alors la fonction est de la pure au monde extérieur.
Donc, pour un impur fonction
f' :: Int -> Int
nous ajoutons de l'Étendue de l'examen
f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the a side effects,
-- then return the new world.
ensuite, f
est pure à nouveau. Nous définissons un paramétrées en type de données IO a = RealWorld -> (a, RealWorld)
, de sorte que nous n'avons pas besoin de type Réaliste, tant de fois
f :: Int -> IO Int
Pour le programmeur, la manipulation d'un RealWorld directement est trop dangereux—en particulier, si un programmeur obtient leurs mains sur une valeur de type RealWorld, ils pourraient essayer de le copier , ce qui est pratiquement impossible. (Pense que d'essayer de copier la totalité du système de fichiers, par exemple. Où placeriez-vous?) Par conséquent, notre définition de IO encapsule les états du monde entier aussi bien.
Ces fonctions impures sont inutiles si on ne peut pas enchaîner les. Envisager
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 à partir de la console, lire ce fichier, puis imprimer le contenu. Comment pourrions-nous le faire si nous avons accès au monde réel des etats-unis?
printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
(contents, world2) = (getContents filename) world1
in (putStrLn contents) world2 -- results in ((), world3)
Nous voir 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
...
Donc, nous pourrions définir un opérateur ~~~
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
ensuite, on pourrait simplement écrire
printFile = getLine ~~~ getContents ~~~ putStrLn
sans toucher le monde réel.
Maintenant, supposons que nous voulons rendre le contenu d'un fichier en majuscules. Uppercasing est une pure fonction
upperCase :: String -> String
Mais de le faire dans le monde réel, il doit retourner une IO String
. Il est facile de lever une telle fonction:
impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)
ceci peut être généralisé:
impurify :: a -> IO a
impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)
de sorte qu' impureUpperCase = impurify . upperCase
, et nous pouvons écrire
printUpperCaseFile =
getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn
(Note: Normalement, nous écrivons getLine ~~~ getContents ~~~ (putStrLn . upperCase)
)
Maintenant, nous allons voir ce que nous avons fait:
- Nous avons défini un opérateur
(~~~) :: IO b -> (b -> IO c) -> IO c
dont les chaînes de deux fonctions impures ensemble
- Nous avons défini une fonction
impurify :: a -> IO a
qui convertit une pure valeur d'impur.
Maintenant, nous faisons l'identification (>>=) = (~~~)
et return = impurity
, et de voir? Nous avons une monade.
(Pour vérifier si c'est vraiment une monade il y a quelques axiomes doivent être remplies:
(1) 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
(2) f >>= return = f
(f ~~~ impurify) a worldX = let (resF, worldY) = impuify a worldX
in f resF worldY
= let (resF, worldY) = (a, worldX)
in f resF worldY
= f a worldX
(3) f >>= (\x -> g x >>= h) = (f >>= g) >>= h
Faire de l'exercice.)