Haskell Language
IO
Ricerca…
Leggere tutti i contenuti dello standard input in una stringa
main = do
input <- getContents
putStr input
Ingresso:
This is an example sentence.
And this one is, too!
Produzione:
This is an example sentence.
And this one is, too!
Nota: questo programma stamperà effettivamente parti dell'output prima che tutto l'input sia stato letto completamente. Ciò significa che, se, per esempio, si usa getContents
su un file 50MiB, la valutazione pigra e il garbage collector di Haskell garantiranno che solo il parti del file attualmente necessarie (leggi: indispensabili per ulteriori esecuzioni) verranno caricate in memoria. Pertanto, il file 50MiB non verrà caricato in memoria in una sola volta.
Lettura di una riga dall'input standard
main = do
line <- getLine
putStrLn line
Ingresso:
This is an example.
Produzione:
This is an example.
Analisi e costruzione di un oggetto da input 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 )
Ingresso:
Type the first number: 9.5
Type the second number: -2.02
Produzione:
9.5 + -2.02 = 7.48
Lettura dagli handle di file
Come in molte altre parti della libreria I / O, le funzioni che utilizzano implicitamente un flusso standard hanno una controparte in System.IO
che esegue lo stesso lavoro, ma con un parametro aggiuntivo a sinistra, di tipo Handle
, che rappresenta lo stream in corso maneggiato. Ad esempio, getLine :: IO String
ha una controparte 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
Contenuto del file example.txt
:
This is an example.
Hello, world!
This is another example.
Ingresso:
Type a filename: example.txt
Produzione:
example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example
Verifica delle condizioni di fine file
Un po 'controintuitivo al modo in cui le librerie di I / O standard della maggior parte degli altri linguaggi lo fanno, isEOF
non richiede di eseguire un'operazione di lettura prima di verificare una condizione EOF; il runtime lo farà per te.
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
Ingresso:
Line #1.
Line #2.
Line #3.
Produzione:
End-of-file reached at line 4.
Leggere le parole da un intero file
In Haskell, ha spesso senso non preoccuparsi affatto degli handle di file , ma semplicemente leggere o scrivere un intero file direttamente dal disco alla memoria † e fare tutto il partizionamento / elaborazione del testo con la struttura dei dati della stringa pura. Questo evita di mescolare la logica di IO e del programma, che può essere di grande aiuto nell'evitare bug.
-- | 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
Se contiene loremipsum.txt
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
allora il programma uscirà
elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem
Qui, mapM_
ha mapM_
l'elenco di tutte le parole nel file e le ha stampate su una riga separata con putStrLn
.
† Se pensi che questo sia uno spreco nella memoria, hai un punto. In realtà, la pigrizia di Haskell può spesso evitare che l'intero file abbia bisogno di risiedere nella memoria simultaneamente ... ma attenzione, questo tipo di IO pigro causa il proprio insieme di problemi. Per le applicazioni critiche per le prestazioni, ha spesso senso imporre l'intero file da leggere contemporaneamente; si può fare questo con la Data.Text
versione di readFile
.
IO definisce l'azione "principale" del tuo programma
Per rendere eseguibile un programma Haskell devi fornire un file con una funzione main
di tipo IO ()
main :: IO ()
main = putStrLn "Hello world!"
Quando Haskell è compilato, esamina qui i dati I / IO
e li trasforma in un eseguibile. Quando eseguiremo questo programma verrà stampato Hello world!
.
Se hai valori di tipo IO a
diversi da quelli main
, non faranno nulla.
other :: IO ()
other = putStrLn "I won't get printed"
main :: IO ()
main = putStrLn "Hello world!"
Compilare questo programma e eseguirlo avrà lo stesso effetto dell'ultimo esempio. Il codice in other
è ignorato.
Per fare in modo che il codice in other
abbia effetti di runtime, devi comporlo in main
. Nessun valore IO
non eventualmente composto in main
avrà alcun effetto di runtime. Per comporre sequenzialmente due valori I / IO
è possibile utilizzare do
notazione:
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
Quando si compila e si esegue questo programma, viene prodotto
Hello world!
I will get printed... but only at the point where I'm composed into main
Si noti che l'ordine delle operazioni è descritto da come other
stati composti in ordine main
e non in ordine di definizione.
Ruolo e scopo dell'IO
Haskell è un linguaggio puro, nel senso che le espressioni non possono avere effetti collaterali. Un effetto collaterale è tutto ciò che l'espressione o la funzione non produce un valore, ad esempio, modifica un contatore globale o stampa sullo standard output.
In Haskell, i calcoli con effetti secondari (in particolare quelli che possono avere un effetto sul mondo reale) sono modellati usando IO
. Strettamente parlando, IO
è un costruttore di tipi, che accetta un tipo e produce un tipo. Ad esempio, IO Int
è il tipo di un calcolo I / O che produce un valore Int
. Il tipo di IO
è astratto e l'interfaccia fornita per IO
garantisce che determinati valori illegali (cioè funzioni con tipi non sensoriali) non possano esistere, assicurando che tutte le funzioni integrate che eseguono IO abbiano un tipo di ritorno racchiuso in IO
.
Quando viene eseguito un programma Haskell, viene eseguito il calcolo rappresentato dal valore Haskell denominato main
, il cui tipo può essere IO x
per qualsiasi tipo x
.
Manipolando i valori di I / O
Ci sono molte funzioni nella libreria standard che forniscono azioni IO
tipiche che un linguaggio di programmazione generico dovrebbe eseguire, come leggere e scrivere su handle di file. Le azioni IO
generali vengono create e combinate principalmente con due funzioni:
(>>=) :: IO a -> (a -> IO b) -> IO b
Questa funzione (in genere chiamata bind ) richiede un'azione IO
e una funzione che restituisce un'azione IO
e produce l'azione IO
che è il risultato dell'applicazione della funzione al valore prodotto dalla prima azione IO
.
return :: a -> IO a
Questa funzione accetta qualsiasi valore (cioè un valore puro) e restituisce il calcolo dell'IO che non fa IO e produce il valore dato. In altre parole, si tratta di un'azione I / O senza intervento.
Ci sono funzioni generali aggiuntive che vengono spesso utilizzate, ma tutte possono essere scritte in base ai due precedenti. Ad esempio, (>>) :: IO a -> IO b -> IO b
è simile a (>>=)
ma il risultato della prima azione viene ignorato.
Un semplice programma che saluta l'utente usando queste funzioni:
main :: IO ()
main =
putStrLn "What is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
Questo programma usa anche putStrLn :: String -> IO ()
e getLine :: IO String
.
Nota: i tipi di determinate funzioni di cui sopra sono in realtà più generali di quei tipi dati (vale a dire >>=
, >>
e return
).
Semantica IO
Il tipo di IO
in Haskell ha una semantica molto simile a quella dei linguaggi di programmazione imperativi. Ad esempio, quando si scrive s1 ; s2
in un linguaggio imperativo per indicare l'istruzione di esecuzione s1
, quindi l'istruzione s2
, si può scrivere s1 >> s2
per modellare la stessa cosa in Haskell.
Tuttavia, la semantica IO
diverge leggermente da ciò che ci si aspetterebbe provenire da un background imperativo. La funzione di return
non interrompe il flusso di controllo - non ha alcun effetto sul programma se viene eseguita un'altra azione IO
in sequenza. Ad esempio, return () >> putStrLn "boom"
stampa correttamente "boom" return () >> putStrLn "boom"
standard output.
La semantica formale di IO
può essere data in termini di uguaglianze semplici che coinvolgono le funzioni nella sezione precedente:
return x >>= f ≡ f x, ∀ f x
y >>= return ≡ return y, ∀ y
(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g
Queste leggi sono generalmente definite identità sinistra, identità destra e composizione, rispettivamente. Possono essere dichiarati in modo più naturale in termini di funzione
(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
(f >=> g) x = (f x) >>= g
come segue:
return >=> f ≡ f, ∀ f
f >=> return ≡ f, ∀ f
(f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h
Lazy IO
Le funzioni che eseguono calcoli I / O sono in genere rigorose, ovvero tutte le azioni precedenti in una sequenza di azioni devono essere completate prima dell'inizio dell'azione successiva. Tipicamente questo è un comportamento utile e atteso - putStrLn "X" >> putStrLn "Y"
dovrebbe stampare "XY". Tuttavia, alcune funzioni di libreria eseguono l'I / O pigramente, ovvero le azioni di I / O richieste per produrre il valore vengono eseguite solo quando il valore viene effettivamente consumato. Esempi di tali funzioni sono getContents
e readFile
. Lazy I / O può ridurre drasticamente le prestazioni di un programma Haskell, quindi quando si utilizzano le funzioni di libreria, è necessario prestare attenzione a quali funzioni sono pigre.
IO e do
la notazione
Haskell fornisce un metodo più semplice per combinare diversi valori di I / O in valori IO più grandi. Questa sintassi speciale è conosciuto come do
la notazione * ed è semplicemente zucchero sintattico per usi del >>=
, >>
e return
funzioni.
Il programma nella sezione precedente può essere scritta in due modi utilizzando do
notazione, la prima è la layout sensibile e il secondo layout essere insensibile:
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
main = do {
putStrLn "What is your name?" ;
name <- getLine ;
putStrLn ("Hello " ++ name ++ "!")
}
Tutti e tre i programmi sono esattamente equivalenti.
* Si noti che do
la notazione è applicabile anche ad una classe più ampia di tipo costruttori chiamati monadi.
Ottenere il "a" "di" IO a "
Una domanda comune è: "Ho un valore IO a
, ma voglio fare qualcosa per che a
valore di:? Come ottengo accesso ad esso" Come si può operare su dati che provengono dal mondo esterno (ad esempio, incrementando un numero digitato dall'utente)?
Il punto è che se si utilizza una funzione pura su dati ottenuti in modo impuro, il risultato è ancora impuro. Dipende da cosa l'utente ha fatto! Un valore di tipo IO a
sta per un "calcolo dell'effetto collaterale che ha come risultato un valore di tipo a
" che può essere eseguito solo (a) componendolo in main
e (b) compilando ed eseguendo il programma. Per questo motivo, non c'è modo all'interno puro mondo Haskell a "ottenere il a
fuori".
Invece, vogliamo costruire un nuovo calcolo, un nuovo IO
valore, che si avvale del a
valore di in fase di esecuzione. Questo è un altro modo per comporre i valori di I / IO
e quindi possiamo usare 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
Qui stiamo usando una funzione di puro ( getMessage
) per accendere un Int
in una String
, ma stiamo usando do
la notazione per farla applicare al risultato di un IO
computazione myComputation
quando (dopo) che il calcolo viene eseguito. Il risultato è un calcolo IO
più grande, newComputation
. Questa tecnica di usare le funzioni pure in un contesto impuro è chiamata sollevamento .
Scrivere su stdout
Secondo la specifica della lingua di Haskell 2010, le seguenti funzioni standard di I / O sono disponibili in Prelude, quindi non sono necessarie importazioni per utilizzarle.
putChar :: Char -> IO ()
- scrive un char
allo stdout
Prelude> putChar 'a'
aPrelude> -- Note, no new line
putStr :: String -> IO ()
- scrive una String
su stdout
Prelude> putStr "This is a string!"
This is a string!Prelude> -- Note, no new line
putStrLn :: String -> IO ()
- scrive una String
su stdout
e aggiunge una nuova riga
Prelude> putStrLn "Hi there, this is another String!"
Hi there, this is another String!
print :: Show a => a -> IO ()
- scrive a
un'istanza di Show
a 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
Ricorda che puoi creare un'istanza di Show
per i tuoi tipi utilizzando 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`
Caricamento e funzionamento in 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>
Leggendo da `stdin`
Come per la specifica della lingua di Haskell 2010 , le seguenti funzioni standard di I / O sono disponibili in Prelude, quindi non sono necessarie importazioni per utilizzarle.
getChar :: IO Char
- leggi un Char
da 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
- legge una String
da stdin
, sans new line character
Prelude> getLine
Hello there! -- user enters some text and presses enter
"Hello there!"
read :: Read a => String -> a
- converti una stringa in un valore
Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True
Altre funzioni meno comuni sono:
-
getContents :: IO String
- restituisce tutti gli input dell'utente come una singola stringa, che viene letta pigramente quando è necessaria -
interact :: (String -> String) -> IO ()
- accetta una funzione di tipo String-> String come argomento. L'intero input dal dispositivo di input standard viene passato a questa funzione come argomento