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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow