• Aucun résultat trouvé

Manipulation des valeurs IO

Dans le document Démarrer avec le langage Haskell (Page 155-158)

Il existe de nombreuses fonctions dans la bibliothèque standard fournissant des actions d' IO typiques qu'un langage de programmation général doit exécuter, telles que la lecture et l'écriture sur des descripteurs de fichiers. Les actions IO générales sont créées et combinées

principalement avec deux fonctions: (>>=) :: IO a -> (a -> IO b) -> IO b

Cette fonction (généralement appelée bind ) prend une action IO et une fonction qui renvoie une action IO et produit l'action IO qui résulte de l'application de la fonction à la valeur produite par la première action IO .

return :: a -> IO a

Cette fonction prend n'importe quelle valeur (c'est-à-dire une valeur pure) et retourne le calcul IO qui ne fait pas IO et produit la valeur donnée. En d'autres termes, il s'agit d'une action d'E / S sans opération.

Il y a des fonctions générales supplémentaires qui sont souvent utilisées, mais toutes peuvent être écrites en fonction des deux précédentes. Par exemple, (>>) :: IO a -> IO b -> IO b est similaire à (>>=) mais le résultat de la première action est ignoré.

Un programme simple accueillant l'utilisateur en utilisant ces fonctions: main :: IO ()

main =

putStrLn "What is your name?" >> getLine >>= \name ->

putStrLn ("Hello " ++ name ++ "!")

Ce programme utilise également putStrLn :: String -> IO () et getLine :: IO String .

Note: les types de certaines fonctions ci-dessus sont en réalité plus généraux que ceux donnés (à savoir >>= , >> et return ).

Sémantique IO

Le type IO dans Haskell a une sémantique très similaire à celle des langages de programmation impératifs. Par exemple, quand on écrit s1 ; s2 dans un langage impératif pour indiquer

l'instruction d'exécution s1 , puis l'instruction s2 , on peut écrire s1 >> s2 pour modéliser la même chose dans Haskell.

Cependant, la sémantique d' IO divergent légèrement de ce que l'on pourrait attendre d'un contexte impératif. La fonction de return n'interrompt pas le flux de contrôle - elle n'a aucun effet sur le programme si une autre action IO est exécutée en séquence. Par exemple, return () >> putStrLn "boom" imprime correctement "boom" sur la sortie standard.

La sémantique formelle de IO peut être donnée en termes d'égalités simples impliquant les fonctions de la section précédente:

return x >>= f ≡ f x, ∀ f x y >>= return ≡ return y, ∀ y

(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g

Ces lois sont généralement appelées identité de gauche, identité correcte et composition, respectivement. Ils peuvent être énoncés plus naturellement en termes de fonction

(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c (f >=> g) x = (f x) >>= g comme suit: return >=> f ≡ f, ∀ f f >=> return ≡ f, ∀ f (f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h

Paresseux IO

Les fonctions exécutant des calculs d'E / S sont généralement strictes, ce qui signifie que toutes les actions précédentes d'une séquence d'actions doivent être terminées avant que l'action suivante ne commence. Typiquement, c'est un comportement utile et attendu - putStrLn "X" >> putStrLn "Y" devrait imprimer "XY". Cependant, certaines fonctions de bibliothèque effectuent des E / S paresseuses, ce qui signifie que les actions d'E / S requises pour produire la valeur ne sont effectuées que lorsque la valeur est réellement consommée. Les exemples de telles fonctions sont getContents et readFile . Les E / S différées peuvent réduire considérablement les

performances d'un programme Haskell. Par conséquent, lors de l'utilisation des fonctions de bibliothèque, il convient de noter les fonctions paresseuses.

IO et

do

notation

Haskell fournit une méthode plus simple pour combiner différentes valeurs d'E / S en plus grandes valeurs d'E / S. Cette syntaxe spéciale est connu comme do notation * et est tout simplement le sucre syntaxique pour des usages de la >>= , >> et return fonctions.

Le programme de la section précédente peut être écrit de deux manières différentes en utilisant la notation do , la première étant sensible à la mise en page et la seconde insensible à la mise en page:

main = do

putStrLn "What is your name?" name <- getLine

main = do {

putStrLn "What is your name?" ; name <- getLine ;

putStrLn ("Hello " ++ name ++ "!") }

Les trois programmes sont exactement équivalents.

* Notez que do notation est également applicable à une classe plus large de constructeurs de types appelés monads .

Obtenir le "un" de "IO a"

Une question commune est «J'ai une valeur de IO a , mais je veux faire quelque chose à ce a valeur: comment puis-je y avoir accès » Comment peut-on opérer sur des données provenant du monde extérieur (par exemple, incrémenter un numéro saisi par l'utilisateur)?

Le fait est que si vous utilisez une fonction pure sur des données obtenues de manière

impeccable, le résultat est encore impur. Cela dépend de ce que l'utilisateur a fait! Une valeur de type IO a signifie un "calcul avec effet secondaire entraînant une valeur de type a " qui ne peut être exécuté qu'en (a) le composant en main et (b) en compilant et en exécutant votre programme. Pour cette raison, il n'y a aucun moyen au sein du monde pur Haskell à « obtenir le a sur ».

Au lieu de cela, nous voulons construire un nouveau calcul, une nouvelle valeur d' IO / IO , qui utilise a valeur à l'exécution . Ceci est une autre façon de composer les valeurs des entrées- IO et, encore une fois, nous pouvons utiliser do -notation:

-- assuming

myComputation :: IO Int getMessage :: Int -> String

getMessage int = "My computation resulted in: " ++ show int newComputation :: IO ()

newComputation = do

int <- myComputation -- we "bind" the result of myComputation to a name, 'int' putStrLn $ getMessage int -- 'int' holds a value of type Int

Ici, nous utilisons une fonction pure ( getMessage ) pour transformer un Int en String , mais nous utilisons la notation do pour l'appliquer au résultat d'un calcul IO myComputation lorsque (après) le calcul s'exécute. Le résultat est un calcul IO plus important, newComputation . Cette technique consistant à utiliser des fonctions pures dans un contexte impur est appelée lever .

Dans le document Démarrer avec le langage Haskell (Page 155-158)

Documents relatifs