Szukaj…


Odczytywanie całej zawartości standardowego wejścia do łańcucha

main = do
    input <- getContents
    putStr input

Wejście:

This is an example sentence.
And this one is, too!

Wynik:

This is an example sentence.
And this one is, too!

Uwaga: Ten program faktycznie wydrukuje części danych wyjściowych, zanim wszystkie dane zostaną w pełni odczytane. Oznacza to, że jeśli na przykład użyjesz getContents w pliku 50 MB, leniwa ocena Haskella i moduł wyrzucania elementów bezużytecznych zapewni, że tylko części pliku, które są obecnie potrzebne (czytaj: niezbędne do dalszego wykonania) zostaną załadowane do pamięci. Dlatego plik 50 MB nie zostanie załadowany do pamięci jednocześnie.

Odczytywanie linii ze standardowego wejścia

main = do
    line <- getLine
    putStrLn line

Wejście:

This is an example.

Wynik:

This is an example.

Analiza i konstruowanie obiektu ze standardowego wejścia

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 )

Wejście:

Type the first number: 9.5
Type the second number: -2.02

Wynik:

9.5 + -2.02 = 7.48

Odczytywanie z uchwytów plików

Podobnie jak w kilku innych częściach biblioteki I / O, funkcje, które domyślnie korzystają ze standardowego strumienia, mają w System.IO odpowiednik, który wykonuje to samo zadanie, ale z dodatkowym parametrem po lewej stronie typu Handle , który reprezentuje strumień będący obsługiwane. Na przykład getLine :: IO String ma odpowiednik 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

Zawartość pliku example.txt :

This is an example.
Hello, world!
This is another example.

Wejście:

Type a filename: example.txt

Wynik:

example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example

Sprawdzanie warunków końca pliku

Nieco intuicyjny w stosunku do sposobu, w jaki robią to standardowe biblioteki I / O większości innych języków, isEOF Haskella nie wymaga wykonania operacji odczytu przed sprawdzeniem warunku EOF; środowisko wykonawcze zrobi to za Ciebie.

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

Wejście:

Line #1.
Line #2.
Line #3.

Wynik:

End-of-file reached at line 4.

Odczytywanie słów z całego pliku

W Haskell często nie ma sensu przejmować się uchwytami plików , ale po prostu czytać lub zapisywać cały plik bezpośrednio z dysku do pamięci i wykonywać całe partycjonowanie / przetwarzanie tekstu przy użyciu czystej struktury danych. Pozwala to uniknąć mieszania operacji wejścia / wyjścia i logiki programu, co może znacznie pomóc w uniknięciu błędów.

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

Jeśli loremipsum.txt zawiera

Lorem ipsum dolor sit amet,
consectetur adipiscing elit

wtedy program wyświetli

elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem

Tutaj mapM_ przejrzał listę wszystkich słów w pliku i wydrukował każde z nich do osobnej linii za pomocą putStrLn .


Jeśli uważasz, że to marnuje pamięć, masz rację. W rzeczywistości lenistwo Haskella często pozwala uniknąć sytuacji, w której cały plik musi znajdować się jednocześnie w pamięci ... ale uwaga, ten rodzaj leniwego IO powoduje własny zestaw problemów. W przypadku aplikacji krytycznych pod względem wydajności często sensowne jest wymuszanie ścisłego odczytu całego pliku; można to zrobić z tym Data.Text wersji readFile .

IO określa akcję główną programu

Aby program Haskell był wykonywalny, musisz dostarczyć plik z main funkcją typu IO ()

main :: IO ()
main = putStrLn "Hello world!"

Podczas kompilacji Haskell sprawdza tutaj dane IO i zamienia je w plik wykonywalny. Kiedy uruchomimy ten program, wydrukuje Hello world! .

Jeśli masz wartości typu IO a inne niż main , nic nie zrobią.

other :: IO ()
other = putStrLn "I won't get printed"

main :: IO ()
main = putStrLn "Hello world!"

Kompilacja i uruchomienie tego programu będzie miało taki sam efekt jak w poprzednim przykładzie. Kod w other jest ignorowany.

Aby kod działał w other środowiskach uruchomieniowych, musisz go skomponować jako main . Żadne wartości IO które ostatecznie nie zostaną skomponowane w main będą miały żadnego wpływu na środowisko wykonawcze. Aby sekwencyjnie skomponować dwie wartości IO , możesz użyć 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

Kiedy kompilujesz i uruchamiasz ten program, generuje on wyniki

Hello world!
I will get printed... but only at the point where I'm composed into main

Zauważ, że kolejność operacji jest opisana przez sposób, w jaki other został złożony w main a nie w porządek definicji.

Rola i cel IO

Haskell to czysty język, co oznacza, że wyrażenia nie mogą mieć skutków ubocznych. Efektem ubocznym jest cokolwiek, co wyrażenie lub funkcja robi poza wytwarzaniem wartości, na przykład modyfikowanie globalnego licznika lub drukowanie na standardowe wyjście.

W Haskell obliczenia uboczne (w szczególności te, które mogą mieć wpływ na rzeczywisty świat) są modelowane przy użyciu IO . Ściśle mówiąc, IO jest konstruktorem typów, bierze typ i wytwarza typ. Na przykład IO Int jest rodzajem obliczeń We / Wy generujących wartość Int . Typ IO jest abstrakcyjny , a interfejs przewidziany dla IO zapewnia, że pewne niedozwolone wartości (tj. Funkcje z typami niesensownymi) nie mogą istnieć, zapewniając, że wszystkie wbudowane funkcje, które wykonują IO, mają typ zwracany zamknięty w IO .

Po uruchomieniu programu Haskell wykonywane jest obliczenie reprezentowane przez wartość Haskell o nazwie main , której typem może być IO x dla dowolnego typu x .

Manipulowanie wartościami We / Wy

W bibliotece standardowej znajduje się wiele funkcji zapewniających typowe operacje we / IO które powinien wykonywać język programowania ogólnego przeznaczenia, takie jak odczytywanie i zapisywanie uchwytów plików. Ogólne działania IO są tworzone i łączone przede wszystkim z dwiema funkcjami:

 (>>=) :: IO a -> (a -> IO b) -> IO b

Ta funkcja (zwykle nazywana wiązaniem ) przyjmuje akcję IO i funkcję, która zwraca akcję IO , i wytwarza akcję IO która jest wynikiem zastosowania funkcji do wartości wygenerowanej przez pierwszą akcję IO .

 return :: a -> IO a

Ta funkcja przyjmuje dowolną wartość (tj. Czystą wartość) i zwraca obliczenia We / Wy, które nie wykonują We / Wy i dają podaną wartość. Innymi słowy, jest to działanie we / wy bez operacji.

Istnieją dodatkowe ogólne funkcje, które są często używane, ale wszystkie można zapisać w kategoriach dwóch powyższych. Na przykład (>>) :: IO a -> IO b -> IO b jest podobny do (>>=) ale wynik pierwszego działania jest ignorowany.

Prosty program witający użytkownika za pomocą tych funkcji:

 main :: IO ()
 main =
   putStrLn "What is your name?" >>
   getLine >>= \name ->
   putStrLn ("Hello " ++ name ++ "!")

Ten program używa również putStrLn :: String -> IO () i getLine :: IO String .


Uwaga: typy niektórych funkcji powyżej są w rzeczywistości bardziej ogólne niż te podane (mianowicie >>= , >> i return ).

Semantyka IO

Typ IO w Haskell ma bardzo podobną semantykę do imperatywnych języków programowania. Na przykład, kiedy pisze się s1 ; s2 w imperatywnym języku dla wskazania wykonania instrukcji s1 , a następnie instrukcji s2 , można napisać s1 >> s2 aby s1 >> s2 to samo w Haskell.

Jednak semantyka IO nieznacznie różni się od tego, czego można oczekiwać od imperatywnego tła. Funkcja return nie przerywa przepływu sterowania - nie ma wpływu na program, jeśli kolejne działanie IO jest uruchamiane po kolei. Na przykład return () >> putStrLn "boom" poprawnie drukuje „boom” na standardowe wyjście.


Formalną semantykę IO można podać w kategoriach prostych równości obejmujących funkcje z poprzedniej sekcji:

 return x >>= f ≡ f x, ∀ f x
 y >>= return ≡ return y, ∀ y
 (m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g

Te prawa są zwykle określane odpowiednio jako lewa tożsamość, prawa tożsamość i skład. Można je określić bardziej naturalnie pod względem funkcji

 (>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
 (f >=> g) x = (f x) >>= g

następująco:

 return >=> f ≡ f, ∀ f
 f >=> return ≡ f, ∀ f
 (f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h

Leniwy IO

Funkcje wykonujące obliczenia We / Wy są zazwyczaj ścisłe, co oznacza, że wszystkie poprzednie akcje w sekwencji akcji muszą zostać zakończone przed rozpoczęciem kolejnej akcji. Zazwyczaj jest to użyteczne i oczekiwane zachowanie - putStrLn "X" >> putStrLn "Y" powinien wypisać „XY”. Jednak niektóre funkcje biblioteczne wykonują operacje we / wy leniwie, co oznacza, że działania we / wy wymagane do wygenerowania wartości są wykonywane tylko wtedy, gdy wartość zostanie faktycznie wykorzystana. Przykładami takich funkcji są getContents i readFile . Leniwe operacje we / wy mogą drastycznie zmniejszyć wydajność programu Haskell, dlatego podczas korzystania z funkcji bibliotecznych należy zwrócić uwagę, które funkcje są leniwe.

IO i do notacja

Haskell zapewnia prostszą metodę łączenia różnych wartości IO w większe wartości IO. Ta specjalna składnia jest znany jako do notacji * i jest po prostu cukier syntaktyczny dla zastosowań tych >>= , >> a return funkcje.

Program w poprzednim rozdziale można zapisać na dwa sposoby: za pomocą do notacji, pierwsza istota układ liter i drugi układ istota niewrażliwa:

 main = do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Hello " ++ name ++ "!")


 main = do {
   putStrLn "What is your name?" ;
   name <- getLine ;
   putStrLn ("Hello " ++ name ++ "!")
   }

Wszystkie trzy programy są dokładnie równoważne.


* Uwaga do notacja dotyczy również szerszej klasy konstruktorów typu zwanych monadami .

Wydobywanie „a” z „IO a”

Częstym pytaniem jest „Mam wartość IO a , ale chcę zrobić coś z tym a wartości: w jaki sposób mogę uzyskać dostęp do niego” Jak można operować danymi pochodzącymi ze świata zewnętrznego (na przykład zwiększając liczbę wpisywaną przez użytkownika)?

Chodzi o to, że jeśli używasz czystej funkcji na danych uzyskanych nieczysto, wynik jest nadal nieczysty. To zależy od tego, co zrobił użytkownik! Wartość typu IO a oznacza „obliczenia powodujące skutki uboczne, w wyniku których powstaje wartość typu a ”, którą można uruchomić tylko poprzez (a) skomponowanie jej w main i (b) kompilację i uruchomienie programu. Z tego powodu, nie ma sposobu, w ciągu czystej Haskell świata, aby „dostać się a out”.

Zamiast tego, chcemy zbudować nowe obliczenia, nowe IO wartości, które wykorzystuje a wartości w czasie wykonywania. Jest to kolejny sposób komponowania IO wartości i tak znowu możemy użyć 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

Tutaj używamy czystą funkcję ( getMessage ) zamienić Int do String , ale używamy do zapis aby go zastosować do wyniku IO obliczeń myComputation gdy (po), który działa obliczeniowych. Rezultatem jest większe obliczenie IO , newComputation . Ta technika używania czystych funkcji w nieczystym kontekście nazywa się podnoszeniem .

Pisanie na standardowe wyjście

Zgodnie ze specyfikacją języka Haskell 2010 następujące są standardowe funkcje IO dostępne w Prelude, więc nie jest wymagany import, aby z nich korzystać.

putChar :: Char -> IO () - zapisuje char na standardowe stdout

Prelude> putChar 'a'
aPrelude>  -- Note, no new line

putStr :: String -> IO () - zapisuje String na standardowe stdout

Prelude> putStr "This is a string!"
This is a string!Prelude>  -- Note, no new line

putStrLn :: String -> IO () - zapisuje String na standardowe stdout i dodaje nową linię

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

Przypomnij sobie, że możesz utworzyć instancję Show dla własnych typów, korzystając z deriving :

-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex")  -- Person is an instance of `Show`, thanks to `deriving`

Ładowanie i uruchamianie w 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>

Czytanie z `stdin`

Zgodnie ze specyfikacją języka Haskell 2010 , poniżej znajdują się standardowe funkcje IO dostępne w Prelude, więc nie jest wymagany import, aby z nich korzystać.

getChar :: IO Char - przeczytaj Char ze 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 - czytaj String ze stdin , bez nowego znaku linii

Prelude> getLine
Hello there!  -- user enters some text and presses enter
"Hello there!"

read :: Read a => String -> a - przekształca ciąg znaków na wartość

Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True

Inne, mniej powszechne funkcje to:

  • getContents :: IO String - zwraca wszystkie dane wejściowe użytkownika jako pojedynczy ciąg, który jest odczytywany leniwie w razie potrzeby
  • interact :: (String -> String) -> IO () - przyjmuje jako argument funkcję typu String-> String. Cały sygnał wejściowy ze standardowego urządzenia wejściowego jest przekazywany do tej funkcji jako argument


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow