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!
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


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow