Haskell Language
IO
Recherche…
Lecture de tous les contenus d'une entrée standard dans une chaîne
main = do
input <- getContents
putStr input
Contribution:
This is an example sentence.
And this one is, too!
Sortie:
This is an example sentence.
And this one is, too!
Remarque: Ce programme imprimera en fait des parties de la sortie avant que toutes les entrées aient été entièrement lues. Par exemple, si vous utilisez getContents
sur un fichier de 50MiB, l’évaluation paresseuse et le ramasse-miettes de Haskell garantissent que seul le fichier les parties du fichier actuellement nécessaires (lire: indispensable pour une exécution ultérieure) seront chargées en mémoire. Ainsi, le fichier 50MiB ne sera pas chargé en mémoire en même temps.
Lecture d'une ligne à partir d'une entrée standard
main = do
line <- getLine
putStrLn line
Contribution:
This is an example.
Sortie:
This is an example.
Analyse et construction d'un objet à partir d'une entrée standard
readFloat :: IO Float
readFloat =
fmap read getLine
main :: IO ()
main = do
putStr "Type the first number: "
first <- readFloat
putStr "Type the second number: "
second <- readFloat
putStrLn $ show first ++ " + " ++ show second ++ " = " ++ show ( first + second )
Contribution:
Type the first number: 9.5
Type the second number: -2.02
Sortie:
9.5 + -2.02 = 7.48
Lecture des descripteurs de fichiers
Comme dans plusieurs autres parties de la bibliothèque d'E / S, les fonctions qui utilisent implicitement un flux standard ont un équivalent dans System.IO
qui effectue le même travail, mais avec un paramètre supplémentaire de type Handle
, représentant le flux en cours. manipulé Par exemple, getLine :: IO String
a un équivalent hGetLine :: Handle -> IO String
.
import System.IO( Handle, FilePath, IOMode( ReadMode ),
openFile, hGetLine, hPutStr, hClose, hIsEOF, stderr )
import Control.Monad( when )
dumpFile :: Handle -> FilePath -> Integer -> IO ()
dumpFile handle filename lineNumber = do -- show file contents line by line
end <- hIsEOF handle
when ( not end ) $ do
line <- hGetLine handle
putStrLn $ filename ++ ":" ++ show lineNumber ++ ": " ++ line
dumpFile handle filename $ lineNumber + 1
main :: IO ()
main = do
hPutStr stderr "Type a filename: "
filename <- getLine
handle <- openFile filename ReadMode
dumpFile handle filename 1
hClose handle
Contenu du fichier example.txt
:
This is an example.
Hello, world!
This is another example.
Contribution:
Type a filename: example.txt
Sortie:
example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example
Vérification des conditions de fin de fichier
Un peu contre-intuitif par rapport à la manière dont les bibliothèques d'E / S standard de la plupart des autres langages le font, isEOF
d'Haskell ne vous oblige pas à effectuer une opération de lecture avant de vérifier une condition EOF; le runtime le fera pour vous.
import System.IO( isEOF )
eofTest :: Int -> IO ()
eofTest line = do
end <- isEOF
if end then
putStrLn $ "End-of-file reached at line " ++ show line ++ "."
else do
getLine
eofTest $ line + 1
main :: IO ()
main =
eofTest 1
Contribution:
Line #1.
Line #2.
Line #3.
Sortie:
End-of-file reached at line 4.
Lire des mots d'un fichier entier
Dans Haskell, il est souvent judicieux de ne pas se préoccuper des descripteurs de fichiers , mais simplement de lire ou d'écrire un fichier entier directement du disque vers la mémoire † , et d'effectuer tout le partitionnement / traitement du texte avec la structure de données pure. Cela évite de mélanger IO et la logique du programme, ce qui peut grandement aider à éviter les bogues.
-- | The interesting part of the program, which actually processes data
-- but doesn't do any IO!
reverseWords :: String -> [String]
reverseWords = reverse . words
-- | A simple wrapper that only fetches the data from disk, lets
-- 'reverseWords' do its job, and puts the result to stdout.
main :: IO ()
main = do
content <- readFile "loremipsum.txt"
mapM_ putStrLn $ reverseWords content
Si loremipsum.txt
contient
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
alors le programme sortira
elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem
Ici, mapM_
a parcouru la liste de tous les mots du fichier et les a imprimés sur une ligne distincte avec putStrLn
.
† Si vous pensez que cela vous fait perdre de la mémoire, vous avez raison. En fait, la paresse de Haskell évite souvent que le fichier entier doive résider simultanément dans la mémoire… mais attention, ce type d’OE paresseux provoque ses propres problèmes. Pour les applications critiques en termes de performances, il est souvent judicieux de forcer la lecture intégrale du fichier en une seule fois; vous pouvez le faire avec la Data.Text
version de readFile
.
IO définit l'action `main` de votre programme
Pour rendre un programme Haskell exécutable, vous devez fournir un fichier avec une fonction main
de type IO ()
main :: IO ()
main = putStrLn "Hello world!"
Lorsque Haskell est compilé, il examine les données d' IO
et les transforme en un exécutable. Lorsque nous exécuterons ce programme, il imprimera Hello world!
.
Si vous avez des valeurs de type IO a
autre que main
elles ne feront rien.
other :: IO ()
other = putStrLn "I won't get printed"
main :: IO ()
main = putStrLn "Hello world!"
Compiler ce programme et le lancer aura le même effet que le dernier exemple. Le code dans l' other
est ignoré.
Afin de rendre le code dans d' other
effets d'exécution, vous devez le composer en main
. Aucune valeur d' IO
/ IO
n'est pas finalement composée dans main
aura un effet d'exécution. Pour composer deux valeurs d' IO
manière séquentielle, vous pouvez utiliser do
-notation:
other :: IO ()
other = putStrLn "I will get printed... but only at the point where I'm composed into main"
main :: IO ()
main = do
putStrLn "Hello world!"
other
Lorsque vous compilez et exécutez ce programme, il affiche
Hello world!
I will get printed... but only at the point where I'm composed into main
Notez que l'ordre des opérations est décrit par la façon dont l' other
été composé dans l'ordre main
et non dans l'ordre de définition.
Rôle et objet des opérations d'information
Haskell est un langage pur, ce qui signifie que les expressions ne peuvent avoir d'effets secondaires. Un effet secondaire est tout ce que l’expression ou la fonction fait d’autre que produire une valeur, par exemple modifier un compteur global ou imprimer en sortie standard.
Dans Haskell, les calculs à effets secondaires (en particulier ceux qui peuvent avoir un effet sur le monde réel) sont modélisés en utilisant IO
. Strictement parlant, IO
est un constructeur de type, prenant un type et produisant un type. Par exemple, IO Int
est le type d'un calcul d'E / S produisant une valeur Int
. Le type IO
est abstrait et l'interface fournie pour IO
garantit que certaines valeurs illégales (c'est-à-dire des fonctions avec des types non sensibles) ne peuvent exister, en veillant à ce que toutes les fonctions intégrées exécutant IO aient un type de retour compris dans IO
.
Lorsqu'un programme Haskell est exécuté, le calcul représenté par la valeur Haskell nommée main
, dont le type peut être IO x
pour tout type x
, est exécuté.
Manipulation des valeurs IO
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
putStrLn ("Hello " ++ name ++ "!")
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 .
Ecrire à stdout
Conformément à la spécification de langage Haskell 2010, les fonctions d'E / S standard disponibles dans Prelude sont les suivantes: aucune importation n'est requise pour les utiliser.
putChar :: Char -> IO ()
- écrit un char
dans stdout
Prelude> putChar 'a'
aPrelude> -- Note, no new line
putStr :: String -> IO ()
- écrit une String
dans stdout
Prelude> putStr "This is a string!"
This is a string!Prelude> -- Note, no new line
putStrLn :: String -> IO ()
- écrit une String
dans stdout
et ajoute une nouvelle ligne
Prelude> putStrLn "Hi there, this is another String!"
Hi there, this is another String!
print :: Show a => a -> IO ()
- écrit a
instance de Show
à stdout
Prelude> print "hi"
"hi"
Prelude> print 1
1
Prelude> print 'a'
'a'
Prelude> print (Just 'a') -- Maybe is an instance of the `Show` type class
Just 'a'
Prelude> print Nothing
Nothing
Rappelez-vous que vous pouvez instancier Show
pour vos propres types en utilisant la deriving
:
-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex") -- Person is an instance of `Show`, thanks to `deriving`
Chargement et exécution dans GHCi:
Prelude> :load ex.hs
[1 of 1] Compiling ex ( ex.hs, interpreted )
Ok, modules loaded: ex.
*Main> main -- from ex.hs
Person {name = "Alex"}
*Main>
Lecture de `stdin`
Conformément à la spécification de langage Haskell 2010 , les fonctions d'E / S standard disponibles dans Prelude sont les suivantes: aucune importation n'est requise pour les utiliser.
getChar :: IO Char
- lire un Char
de stdin
-- MyChar.hs
main = do
myChar <- getChar
print myChar
-- In your shell
runhaskell MyChar.hs
a -- you enter a and press enter
'a' -- the program prints 'a'
getLine :: IO String
- getLine :: IO String
une String
de stdin
, sans nouvelle ligne
Prelude> getLine
Hello there! -- user enters some text and presses enter
"Hello there!"
read :: Read a => String -> a
- convertit une String en une valeur
Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True
D'autres fonctions moins courantes sont:
-
getContents :: IO String
- Retourne toutes les entrées utilisateur sous la forme d'une chaîne unique, qui est lue paresseusement au fur et à mesure des besoins -
interact :: (String -> String) -> IO ()
- prend une fonction de type String-> String comme argument. L'intégralité de l'entrée du périphérique d'entrée standard est transmise à cette fonction en tant qu'argument