Haskell Language
IO
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!
print :: Show a => a -> IO ()
- pisze a
wystąpienie Show
na 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
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