Haskell Language
IO
Sök…
Läser allt innehåll i standardinmatning i en sträng
main = do
input <- getContents
putStr input
Inmatning:
This is an example sentence.
And this one is, too!
Produktion:
This is an example sentence.
And this one is, too!
Obs! Detta program kommer faktiskt att skriva ut delar av utmatningen innan all ingång har lästs fullständigt in. Det innebär att om du till exempel använder getContents
över en 50MiB-fil kommer Haskells lata utvärdering och skräpkollektor att säkerställa att endast delar av filen som för närvarande behövs (läs: oumbärlig för vidare körning) laddas i minnet. Således kommer 50MiB-filen inte att laddas i minnet på en gång.
Läser en rad från standardinmatning
main = do
line <- getLine
putStrLn line
Inmatning:
This is an example.
Produktion:
This is an example.
Analysera och konstruera ett objekt från standardinmatning
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 )
Inmatning:
Type the first number: 9.5
Type the second number: -2.02
Produktion:
9.5 + -2.02 = 7.48
Avläsning från filhandtag
Liksom i flera andra delar av I / O-biblioteket har funktioner som implicit använder en standardström en motsvarighet i System.IO
som utför samma jobb, men med en extra parameter till vänster, av typen Handle
, som representerar strömmen som hanteras. Till exempel har getLine :: IO String
en motsvarighet 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
Innehållet i filen example.txt
:
This is an example.
Hello, world!
This is another example.
Inmatning:
Type a filename: example.txt
Produktion:
example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example
Kontrollera om villkoren i slutet av filen
Lite motintuitivt mot hur de flesta andra språk standard I / O-bibliotek gör det, Haskells isEOF
kräver inte att du utför en läsoperation innan du kontrollerar för ett EOF-tillstånd; runtime kommer att göra det åt dig.
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
Inmatning:
Line #1.
Line #2.
Line #3.
Produktion:
End-of-file reached at line 4.
Läser ord från en hel fil
I Haskell är det ofta vettigt att inte bry sig om filhanteringen alls, utan bara läsa eller skriva en hel fil direkt från disk till minne † och göra all partitionering / bearbetning av texten med den rena strängdatastrukturen. Detta undviker att blanda IO och programlogik, vilket i hög grad kan hjälpa till att undvika buggar.
-- | 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
Om loremipsum.txt
innehåller
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
sedan kommer programmet att matas ut
elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem
Här gick mapM_
igenom listan över alla ord i filen och skrev ut var och en av dem på en separat rad med putStrLn
.
† Om du tycker att det är slöseri med minnet har du en poäng. Egentligen kan Haskells latitet ofta undvika att hela filen behöver ligga i minnet samtidigt ... men se upp, den här typen av lat IO orsakar sin egen uppsättning problem. För prestandakritiska applikationer är det ofta vettigt att verkställa hela filen som ska läsas samtidigt, strikt; Du kan göra detta med Data.Text
versionen av readFile
.
IO definierar programmets huvudsakliga åtgärd
För att göra en Haskell program körbara du måste ge en fil med en main
av typen IO ()
main :: IO ()
main = putStrLn "Hello world!"
När Haskell sammanställs granskar den IO
uppgifterna här och gör dem till en körbar. När vi kör detta program kommer det att skriva ut Hello world!
.
Om du har värden av typ IO a
annan än main
kommer de inte att göra någonting.
other :: IO ()
other = putStrLn "I won't get printed"
main :: IO ()
main = putStrLn "Hello world!"
Att sammanställa detta program och köra det kommer att ha samma effekt som det sista exemplet. Koden i other
ignoreras.
För att göra koden i other
har runtime-effekter måste du komponera den till main
. Inga IO
värden som så småningom komponeras till main
kommer att ha någon runtime-effekt. För att komponera två IO
värden i följd kan du använda 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
När du sammanställer och kör detta program matar det ut
Hello world!
I will get printed... but only at the point where I'm composed into main
Observera att åtgärdsordningen beskrivs av hur other
komponerades till main
och inte definitionsordningen.
Roll och syfte med IO
Haskell är ett rent språk, vilket betyder att uttryck inte kan ha biverkningar. En bieffekt är allt som uttrycket eller funktionen gör annat än att producera ett värde, till exempel modifiera en global räknare eller skriva ut till standardutmatning.
I Haskell modelleras sidoeffektiva beräkningar (specifikt de som kan ha en effekt på den verkliga världen) med IO
. Strängt taget är IO
en typkonstruktör, tar en typ och producerar en typ. Till exempel är IO Int
typen av en I / O-beräkning som producerar ett Int
värde. IO
typen är abstrakt , och gränssnittet som tillhandahålls för IO
säkerställer att vissa olagliga värden (det vill säga funktioner med icke-sensiska typer) inte kan existera, genom att säkerställa att alla inbyggda funktioner som utför IO har en returtyp innesluten i IO
.
När ett Haskell-program körs, exekveras beräkningen representerad av Haskell-värdet namnet main
, vars typ kan vara IO x
för vilken typ av x
helst.
Manipulera IO-värden
Det finns många funktioner i standardbiblioteket som tillhandahåller typiska IO
åtgärder som ett programmeringsspråk för allmänt bruk ska utföra, till exempel läsning och skrivning till filhandtag. Allmänna IO
åtgärder skapas och kombineras främst med två funktioner:
(>>=) :: IO a -> (a -> IO b) -> IO b
Denna funktion (vanligtvis kallad bind ) tar en IO
åtgärd och en funktion som returnerar en IO
åtgärd och producerar IO
handlingen som är resultatet av att applicera funktionen till det värde som produceras av den första IO
åtgärden.
return :: a -> IO a
Denna funktion tar valfritt värde (dvs. ett rent värde) och returnerar IO-beräkningen som inte gör någon IO och producerar det givna värdet. Med andra ord är det en no-op I / O-åtgärd.
Det finns ytterligare allmänna funktioner som ofta används, men alla kan skrivas i termer av de två ovan. Till exempel (>>) :: IO a -> IO b -> IO b
liknar (>>=)
men resultatet av den första åtgärden ignoreras.
Ett enkelt program som hälsar användaren med hjälp av dessa funktioner:
main :: IO ()
main =
putStrLn "What is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
Detta program använder också putStrLn :: String -> IO ()
och getLine :: IO String
.
Obs: typerna av vissa funktioner ovan är faktiskt mer allmänna än de angivna typerna (nämligen >>=
, >>
och return
).
IO semantik
IO
typen i Haskell har mycket liknande semantik som det som krävs för programmeringsspråk. Till exempel när man skriver s1 ; s2
på ett nödvändigt språk för att indikera exekverande uttalande s1
, sedan uttalande s2
, man kan skriva s1 >> s2
att modellera samma sak i Haskell.
Men semantiken av IO
avvika något om vad som kan förväntas komma från en nödvändighet bakgrund. Den return
fungerar inte avbrottsflödeskontroll - det har ingen effekt på programmet om en annan IO
åtgärder körs i följd. Till exempel return () >> putStrLn "boom"
korrekt "bom" till standardutgång.
Den formella semantiken för IO
kan ges i termer av enkla jämlikheter som involverar funktionerna i föregående avsnitt:
return x >>= f ≡ f x, ∀ f x
y >>= return ≡ return y, ∀ y
(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g
Dessa lagar benämns vanligtvis respektive vänsteridentitet, högeridentitet och sammansättning. De kan anges mer naturligt när det gäller funktionen
(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
(f >=> g) x = (f x) >>= g
som följer:
return >=> f ≡ f, ∀ f
f >=> return ≡ f, ∀ f
(f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h
Lazy IO
Funktioner som utför I / O-beräkningar är vanligtvis strikta, vilket innebär att alla föregående åtgärder i en sekvens av åtgärder måste slutföras innan nästa åtgärd påbörjas. Vanligtvis är detta användbart och förväntat beteende - putStrLn "X" >> putStrLn "Y"
ska skriva ut "XY". Vissa biblioteksfunktioner utför emellertid I / O lata, vilket innebär att de I / O-åtgärder som krävs för att producera värdet endast utförs när värdet faktiskt konsumeras. Exempel på sådana funktioner är getContents
och readFile
. Lat I / O kan drastiskt minska prestandan för ett Haskell-program, så vid användning av biblioteksfunktioner bör man vara noga med att notera vilka funktioner som är lata.
IO och do
notation
Haskell tillhandahåller en enklare metod för att kombinera olika IO-värden till större IO-värden. Denna speciella syntax kallas do
notation * och är helt enkelt syntaktiskt socker för användningsområden av >>=
, >>
och return
Programmet i föregående avsnitt kan skrivas på två olika sätt med hjälp av do
notation, den första är layout känslig och den andra är layout okänslig:
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
main = do {
putStrLn "What is your name?" ;
name <- getLine ;
putStrLn ("Hello " ++ name ++ "!")
}
Alla tre program är exakt likvärdiga.
* Observera att do
notation gäller även för en bredare grupp av typ konstruktörer kallas monader.
Ta ut 'a' 'från' 'IO a'
En vanlig fråga är "Jag har ett värde på IO a
, men jag vill göra något för att a
värde: hur får jag tillgång till det?" Hur kan man arbeta med data som kommer från omvärlden (till exempel genom att öka ett nummer som skrivs av användaren)?
Poängen är att om du använder en ren funktion på data som erhålls omedelbart, är resultatet fortfarande orent. Det beror på vad användaren gjorde! Ett värde av typ IO a
står för en "sidoeffektiv beräkning som resulterar i ett värde av typ a
" som bara kan köras genom (a) att komponera det till main
och (b) sammanställa och köra ditt program. Därför finns det inget sätt inom ren Haskell världen "få a
out".
Istället vill vi bygga en ny beräkning, ett nytt IO
värde, som använder sig av a
värde vid körning . Detta är ett annat sätt att komponera IO
värden och så kan vi återigen använda 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
Här använder vi en ren funktion ( getMessage
) för att slå en Int
till en String
, men vi använder do
notation för att göra det appliceras på resultatet av en IO
beräknings myComputation
när (efter) som beräknings körningar. Resultatet är en större IO
beräkning, newComputation
. Denna teknik att använda rena funktioner i ett orent sammanhang kallas lyft .
Skrivande till stdout
Enligt Haskell 2010-språkspecifikationen är följande standard-IO-funktioner tillgängliga i Prelude, så ingen import krävs för att använda dem.
putChar :: Char -> IO ()
- skriver en char
till stdout
Prelude> putChar 'a'
aPrelude> -- Note, no new line
putStr :: String -> IO ()
- skriver en String
till stdout
Prelude> putStr "This is a string!"
This is a string!Prelude> -- Note, no new line
putStrLn :: String -> IO ()
- skriver en String
till stdout
och lägger till en ny rad
Prelude> putStrLn "Hi there, this is another String!"
Hi there, this is another String!
print :: Show a => a -> IO ()
- skriver a
instans av Show
till 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
Kom ihåg att du kan instansera Show
för dina egna typer med deriving
:
-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex") -- Person is an instance of `Show`, thanks to `deriving`
Laddar och kör i 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>
Läser från `stdin`
I enlighet med Haskell 2010 Language Specification är följande standard IO-funktioner tillgängliga i Prelude, så ingen import krävs för att använda dem.
getChar :: IO Char
- läs en Char
från 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
- läs en String
från stdin
, sans ny radkaraktär
Prelude> getLine
Hello there! -- user enters some text and presses enter
"Hello there!"
read :: Read a => String -> a
- konvertera en sträng till ett värde
Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True
Andra, mindre vanliga funktioner är:
-
getContents :: IO String
- returnerar all användarinmatning som en enda sträng, som läses lata när det behövs -
interact :: (String -> String) -> IO ()
- tar en funktion av typen String-> String som dess argument. Hela ingången från standardinmatningen överförs till denna funktion som dess argument