Haskell Language
IO
Zoeken…
Alle inhoud van standaardinvoer lezen in een tekenreeks
main = do
input <- getContents
putStr input
Invoer:
This is an example sentence.
And this one is, too!
Output:
This is an example sentence.
And this one is, too!
Opmerking: Dit programma zal in feite voor alle ingangs- delen van de output af te drukken is volledig ingelezen Dit betekent dat, als, bijvoorbeeld, die u gebruikt. getContents
over een 50MiB bestand, wordt Haskell lui evaluatie en de garbage collector dat alleen de zorgen delen van het bestand die momenteel nodig zijn (lees: onmisbaar voor verdere uitvoering) worden in het geheugen geladen. Het bestand van 50 MB wordt dus niet meteen in het geheugen geladen.
Een regel lezen van standaardinvoer
main = do
line <- getLine
putStrLn line
Invoer:
This is an example.
Output:
This is an example.
Een object parseren en construeren vanuit standaardinvoer
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 )
Invoer:
Type the first number: 9.5
Type the second number: -2.02
Output:
9.5 + -2.02 = 7.48
Lezen van bestandshandvatten
Net als in verschillende andere delen van de I / O-bibliotheek, hebben functies die impliciet een standaardstream gebruiken een tegenhanger in System.IO
die dezelfde taak uitvoert, maar met een extra parameter aan de linkerkant, van het type Handle
, die de stream vertegenwoordigt die wordt afgehandeld. getLine :: IO String
heeft bijvoorbeeld een tegenhanger 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
Inhoud van het bestand example.txt
:
This is an example.
Hello, world!
This is another example.
Invoer:
Type a filename: example.txt
Output:
example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example
Controleren op voorwaarden voor einde bestand
Een beetje contra-intuïtief voor de manier waarop de standaard I / O-bibliotheken van de meeste andere talen dit doen, Haskell's isEOF
vereist niet dat je een leesbewerking uitvoert voordat je op een EOF-toestand controleert; de runtime zal het voor u doen.
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
Invoer:
Line #1.
Line #2.
Line #3.
Output:
End-of-file reached at line 4.
Woorden uit een heel bestand lezen
In Haskell is het vaak zinvol om helemaal niet met bestandshandvatten bezig te zijn, maar gewoon een heel bestand rechtstreeks van schijf naar geheugen te lezen of te schrijven † , en alle partities / verwerking van de tekst met de pure string datastructuur te doen. Dit vermijdt het mengen van IO en programmalogica, wat enorm kan helpen bugs te vermijden.
-- | 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
Als loremipsum.txt
bevat
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
dan wordt het programma uitgevoerd
elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem
Hier ging mapM_
door de lijst met alle woorden in het bestand en drukte ze elk op een afzonderlijke regel af met putStrLn
.
† Als je denkt dat dit verspilling van geheugen is, heb je een punt. Haskell's luiheid kan eigenlijk vaak voorkomen dat het hele bestand tegelijkertijd in het geheugen moet worden bewaard ... maar pas op, dit soort luie IO veroorzaakt zijn eigen problemen. Voor prestatie-kritische applicaties is het vaak zinvol om het hele bestand strikt strikt te lezen. u kunt dit doen met de Data.Text
versie van readFile
.
IO definieert de `hoofd` actie van uw programma
Om een Haskell programma te maken uitvoerbaar u een bestand met een moet verstrekken main
functie van het type IO ()
main :: IO ()
main = putStrLn "Hello world!"
Wanneer Haskell wordt gecompileerd, onderzoekt het hier de IO
gegevens en verandert het in een uitvoerbaar bestand. Wanneer we dit programma uitvoeren, wordt Hello world!
afgedrukt Hello world!
.
Als u waarden van type IO a
andere dan main
, zullen ze niets doen.
other :: IO ()
other = putStrLn "I won't get printed"
main :: IO ()
main = putStrLn "Hello world!"
Dit programma compileren en uitvoeren heeft hetzelfde effect als het laatste voorbeeld. De code in other
wordt genegeerd.
Om ervoor te zorgen dat de code in other
runtime-effecten komt, moet u deze in main
samenstellen . Geen IO
waarden die uiteindelijk niet in de main
zijn opgenomen, hebben een runtime-effect. Om twee IO
waarden opeenvolgend samen te stellen, kunt u do
-notatie gebruiken:
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
Wanneer u dit programma compileert en uitvoert , wordt het uitgevoerd
Hello world!
I will get printed... but only at the point where I'm composed into main
Merk op dat de volgorde van bewerkingen wordt beschreven door hoe other
in main
is samengesteld en niet de definitie-volgorde.
Rol en doel van IO
Haskell is een pure taal, wat betekent dat uitdrukkingen geen bijwerkingen kunnen hebben. Een neveneffect is alles wat de uitdrukking of functie anders doet dan een waarde produceren, bijvoorbeeld een globale teller wijzigen of afdrukken naar standaarduitvoer.
In Haskell worden neveneffectieve berekeningen (met name die welke een effect kunnen hebben op de echte wereld) gemodelleerd met behulp van IO
. Strikt genomen is IO
een typebouwer die een type neemt en een type produceert. IO Int
is bijvoorbeeld het type I / O-berekening dat een Int
waarde Int
. Het IO
type is abstract en de interface die wordt geboden voor IO
zorgt ervoor dat bepaalde illegale waarden (dat wil zeggen functies met niet-sensuele typen) niet kunnen bestaan, door ervoor te zorgen dat alle ingebouwde functies die IO uitvoeren, een retourtype hebben dat is ingesloten in IO
.
Wanneer een Haskell-programma wordt uitgevoerd, wordt de berekening uitgevoerd die wordt weergegeven door de Haskell-waarde main
, waarvan het type IO x
kan zijn voor elk type x
.
IO-waarden manipuleren
Er zijn veel functies in de standaardbibliotheek die typische IO
acties bieden die een programmeertaal voor algemene doeleinden zou moeten uitvoeren, zoals lezen en schrijven naar bestandshandvatten. Algemene IO
acties worden gemaakt en voornamelijk gecombineerd met twee functies:
(>>=) :: IO a -> (a -> IO b) -> IO b
Deze functie (meestal bind genoemd ) neemt een IO
actie en een functie die een IO
actie retourneert, en produceert de IO
actie die het resultaat is van het toepassen van de functie op de waarde die wordt geproduceerd door de eerste IO
actie.
return :: a -> IO a
Deze functie neemt elke waarde (dwz een pure waarde) en retourneert de IO-berekening die geen IO doet en de gegeven waarde produceert. Met andere woorden, het is een no-op I / O-actie.
Er zijn extra algemene functies die vaak worden gebruikt, maar ze kunnen allemaal worden beschreven in termen van de twee hierboven. (>>) :: IO a -> IO b -> IO b
is vergelijkbaar met (>>=)
maar het resultaat van de eerste actie wordt genegeerd.
Een eenvoudig programma dat de gebruiker begroet met behulp van deze functies:
main :: IO ()
main =
putStrLn "What is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
Dit programma gebruikt ook putStrLn :: String -> IO ()
en getLine :: IO String
.
Opmerking: de typen van bepaalde functies hierboven zijn eigenlijk algemener dan de gegeven typen (namelijk >>=
, >>
en return
).
IO semantiek
Het IO
type in Haskell heeft zeer vergelijkbare semantiek als die van imperatieve programmeertalen. Bijvoorbeeld wanneer men s1 ; s2
schrijft s1 ; s2
in een gebiedende taal om aan te geven dat instructie s1
en vervolgens instructie s2
, kan men s1 >> s2
om hetzelfde in Haskell te modelleren.
De semantiek van IO
wijkt echter enigszins af van wat verwacht zou kunnen worden vanuit een imperatieve achtergrond. De return
onderbreekt de besturingsstroom niet - het heeft geen effect op het programma als een andere IO
actie in volgorde wordt uitgevoerd. Bijvoorbeeld, return () >> putStrLn "boom"
drukt "boom" correct af naar standaarduitvoer.
De formele semantiek van IO
kan worden gegeven in termen van eenvoudige gelijkheden met betrekking tot de functies in de vorige sectie:
return x >>= f ≡ f x, ∀ f x
y >>= return ≡ return y, ∀ y
(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g
Deze wetten worden doorgaans respectievelijk linkse identiteit, rechteridentiteit en samenstelling genoemd. Ze kunnen natuurlijker worden gezegd in termen van de functie
(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
(f >=> g) x = (f x) >>= g
als volgt:
return >=> f ≡ f, ∀ f
f >=> return ≡ f, ∀ f
(f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h
Luie IO
Functies die I / O-berekeningen uitvoeren, zijn doorgaans strikt, wat betekent dat alle voorgaande acties in een reeks acties moeten worden voltooid voordat de volgende actie wordt gestart. Meestal is dit nuttig en verwacht gedrag - putStrLn "X" >> putStrLn "Y"
moet "XY" afdrukken. Bepaalde bibliotheekfuncties voeren echter lui I / O uit, wat betekent dat de I / O-acties die nodig zijn om de waarde te produceren, alleen worden uitgevoerd wanneer de waarde daadwerkelijk wordt verbruikt. Voorbeelden van dergelijke functies zijn getContents
en readFile
. Lazy I / O kan de prestaties van een Haskell-programma drastisch verminderen, dus als u bibliotheekfuncties gebruikt, moet u oppassen welke functies lui zijn.
IO en do
notatie
Haskell biedt een eenvoudigere methode voor het combineren van verschillende IO-waarden in grotere IO-waarden. Deze speciale syntax is bekend als do
notatie * en is gewoon syntactische suiker voor het gebruiken van de >>=
, >>
en return
functies.
Het programma in de vorige paragraaf kan worden geschreven op twee verschillende manieren met behulp van do
notatie, waarvan de eerste lay-out-gevoelig is en de tweede layout ongevoelig:
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
main = do {
putStrLn "What is your name?" ;
name <- getLine ;
putStrLn ("Hello " ++ name ++ "!")
}
Alle drie programma's zijn exact gelijkwaardig.
* Merk op dat do
notatie is ook van toepassing op een ruimere klasse van het type constructeurs genoemd monaden.
Haal de 'a' uit '' IO a '
Een veel voorkomende vraag is: "Ik heb een waarde van IO a
, maar ik wil iets doen aan die a
waarde: hoe krijg ik toegang tot deze" Hoe kan men werken met gegevens die van de buitenwereld afkomstig zijn (bijvoorbeeld een door de gebruiker getypt getal ophogen)?
Het punt is dat als u een pure functie gebruikt voor gegevens die onzuiver zijn verkregen, het resultaat nog steeds onzuiver is. Het hangt ervan af wat de gebruiker heeft gedaan! Een waarde van type IO a
staat voor een "neveneffectberekening die resulteert in een waarde van type a
" die alleen kan worden uitgevoerd door (a) deze samen te stellen in main
en (b) uw programma te compileren en uit te voeren. Om die reden is er geen manier om binnen pure Haskell wereld om "het a
out".
In plaats daarvan willen we een nieuwe berekening, een nieuw te bouwen IO
waarde, die gebruik maakt van de a
waarde bij runtime. Dit is een andere manier om IO
waarden samen te stellen en dus kunnen we opnieuw do
-notation gebruiken:
-- 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
Hier zijn we met behulp van een pure functie ( getMessage
) om een te zetten Int
in een String
, maar we gebruiken do
notatie te maken worden toegepast op het resultaat van een IO
berekening myComputation
wanneer (na) die berekening runs. Het resultaat is een grotere IO
berekening, newComputation
. Deze techniek om pure functies in een onzuivere context te gebruiken, wordt lifting genoemd .
Schrijven naar stdout
Volgens de Haskell 2010-taalspecificatie zijn de volgende standaard IO-functies beschikbaar in Prelude, dus er is geen invoer vereist om ze te gebruiken.
putChar :: Char -> IO ()
- schrijft een char
naar stdout
Prelude> putChar 'a'
aPrelude> -- Note, no new line
putStr :: String -> IO ()
- schrijft een String
naar stdout
Prelude> putStr "This is a string!"
This is a string!Prelude> -- Note, no new line
putStrLn :: String -> IO ()
- schrijft een String
naar stdout
en voegt een nieuwe regel toe
Prelude> putStrLn "Hi there, this is another String!"
Hi there, this is another String!
print :: Show a => a -> IO ()
- schrijft a
een voorbeeld van Show
naar 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
Bedenk dat u Show
voor uw eigen typen kunt instantiëren met behulp van deriving
:
-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex") -- Person is an instance of `Show`, thanks to `deriving`
Laden en uitvoeren 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>
Lezen van `stdin`
Volgens de Haskell 2010-taalspecificatie zijn de volgende standaard IO-functies beschikbaar in Prelude, dus er is geen invoer vereist om ze te gebruiken.
getChar :: IO Char
- lees een Char
van 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
- lees een String
van stdin
, zonder nieuw lijnteken
Prelude> getLine
Hello there! -- user enters some text and presses enter
"Hello there!"
read :: Read a => String -> a
- converteer een String naar een waarde
Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True
Andere, minder gebruikelijke functies zijn:
-
getContents :: IO String
- retourneert alle gebruikersinvoer als een enkele string, die lui wordt gelezen als dat nodig is -
interact :: (String -> String) -> IO ()
- neemt een functie van het type String-> String als argument. De volledige invoer van het standaardinvoerapparaat wordt als argument doorgegeven aan deze functie