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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow