Haskell Language
IO
Поиск…
Чтение всего содержимого стандартного ввода в строку
main = do
input <- getContents
putStr input
Входные данные:
This is an example sentence.
And this one is, too!
Выход:
This is an example sentence.
And this one is, too!
Примечание. Эта программа будет фактически печатать части вывода до того, как все входные данные будут полностью прочитаны. Это означает, что, если, например, вы используете getContents
поверх файла 50MiB, ленивая оценка Haskell и сборщик мусора гарантируют, что только части файла, которые в настоящее время необходимы (чтение: незаменим для дальнейшего выполнения), будут загружены в память. Таким образом, файл 50MiB не будет загружен сразу в память.
Чтение строки со стандартного ввода
main = do
line <- getLine
putStrLn line
Входные данные:
This is an example.
Выход:
This is an example.
Анализ и построение объекта со стандартного ввода
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 )
Входные данные:
Type the first number: 9.5
Type the second number: -2.02
Выход:
9.5 + -2.02 = 7.48
Чтение из файлов
Как и в нескольких других частях библиотеки ввода-вывода, функции, которые неявно используют стандартный поток, имеют аналог в System.IO
который выполняет одно и то же задание, но с дополнительным параметром слева, типа Handle
, который представляет поток, являющийся обрабатываются. Например, getLine :: IO String
имеет экземпляр 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
Содержимое файла example.txt
:
This is an example.
Hello, world!
This is another example.
Входные данные:
Type a filename: example.txt
Выход:
example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example
Проверка условий окончания файла
Немного интуитивно понятный способ использования стандартных библиотек ввода / вывода большинства других языков, isEOF
не требует выполнения операции чтения перед проверкой состояния EOF; время выполнения сделает это за вас.
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
Входные данные:
Line #1.
Line #2.
Line #3.
Выход:
End-of-file reached at line 4.
Чтение слов из целого файла
В Haskell часто имеет смысл не беспокоить файловые дескрипторы вообще, а просто читать или записывать весь файл прямо с диска на память † и выполнять всю секцию / обработку текста с чистой строковой структурой данных. Это позволяет избежать смешивания IO и логики программ, что может значительно помочь избежать ошибок.
-- | 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
Если loremipsum.txt
содержит
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
то программа выведет
elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem
Здесь mapM_
просмотрел список всех слов в файле и напечатал каждую из них в отдельной строке с помощью putStrLn
.
† Если вы считаете, что это бесполезно в памяти, у вас есть точка. Фактически, лента Haskell часто может избежать того, что весь файл должен постоянно находиться в памяти ... но будьте осторожны, этот ленивый IO вызывает свой собственный набор проблем. Для критически важных приложений часто имеет смысл принудительно читать весь файл, строго; вы можете сделать это с помощью Data.Text
версии readFile
.
IO определяет действие `main` вашей программы
Чтобы выполнить исполняемый файл программы Haskell, вы должны предоставить файл с main
функцией типа IO ()
main :: IO ()
main = putStrLn "Hello world!"
Когда Haskell скомпилирован, он анализирует данные IO
здесь и превращает его в исполняемый файл. Когда мы запустим эту программу, она будет печатать Hello world!
,
Если у вас есть значения типа IO a
отличные от main
они ничего не сделают.
other :: IO ()
other = putStrLn "I won't get printed"
main :: IO ()
main = putStrLn "Hello world!"
Компиляция этой программы и ее запуск будут иметь тот же эффект, что и последний пример. Код в other
игнорируется.
Для того чтобы код в other
имел эффекты времени исполнения, вы должны составить его в main
. Никакие значения IO
конечном итоге не состоят в main
будут иметь никакого эффекта времени исполнения. Чтобы составить два значения IO
последовательно, вы можете использовать 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
Когда вы компилируете и запускаете эту программу, она выводит
Hello world!
I will get printed... but only at the point where I'm composed into main
Обратите внимание, что порядок операций описывается тем, как other
составлялись в main
а не в порядке определения.
Роль и цель IO
Haskell - это чистый язык, что означает, что выражения не могут иметь побочных эффектов. Побочным эффектом является то, что выражение или функция выполняет не только значение, например, изменение глобального счетчика или печать на стандартный вывод.
В Haskell боковые эффекты (в частности, те, которые могут влиять на реальный мир) моделируются с использованием IO
. Строго говоря, IO
является конструктором типа, беря тип и создавая тип. Например, IO Int
- это тип вычисления ввода-вывода, производящий значение Int
. Тип IO
является абстрактным , а интерфейс, предоставляемый для IO
гарантирует, что некоторые незаконные значения (то есть функции с нечувствительными типами) не могут существовать, гарантируя, что все встроенные функции, которые выполняют IO, имеют тип возврата, заключенный в IO
.
Когда запускается программа Haskell, выполняется вычисление, представленное значением Haskell с именем main
, тип которого может быть IO x
для любого типа x
.
Манипулирование значениями ввода-вывода
В стандартной библиотеке имеется множество функций, обеспечивающих типичные действия IO
которые должен выполнять язык программирования общего назначения, например чтение и запись в дескрипторы файлов. Общие действия IO
создаются и объединяются в основном с двумя функциями:
(>>=) :: IO a -> (a -> IO b) -> IO b
Эта функция (обычно называемая связыванием ) принимает IO
действие и функцию, которая возвращает действие IO
, и производит действие IO
которое является результатом применения функции к значению, полученному первым действием IO
.
return :: a -> IO a
Эта функция принимает любое значение (т. Е. Чистое значение) и возвращает вычисление IO, которое не делает IO и не дает заданное значение. Другими словами, это операция ввода-вывода без операции.
Существуют дополнительные общие функции, которые часто используются, но все они могут быть записаны в терминах двух выше. Например, (>>) :: IO a -> IO b -> IO b
аналогичен (>>=)
но результат первого действия игнорируется.
Простая программа приветствует пользователя, используя следующие функции:
main :: IO ()
main =
putStrLn "What is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
Эта программа также использует putStrLn :: String -> IO ()
и getLine :: IO String
.
Примечание: типы определенных выше функций на самом деле более общие, чем те, которые указаны (а именно: >>=
, >>
и return
).
Семантика ввода-вывода
Тип IO
в Haskell имеет очень схожую семантику с языком императивных языков программирования. Например, когда записывается s1 ; s2
на императивном языке, чтобы указать исполняющий оператор s1
, затем оператор s2
, можно написать s1 >> s2
чтобы моделировать одно и то же в Haskell.
Тем не менее, семантика IO
немного отличается от того, что можно ожидать от императивного фона. Функция return
не прерывает поток управления - она не влияет на программу, если другое действие IO
выполняется последовательно. Например, return () >> putStrLn "boom"
корректно выводит «стрелу» на стандартный вывод.
Формальная семантика IO
может быть задана в терминах простых равенств, связанных с функциями в предыдущем разделе:
return x >>= f ≡ f x, ∀ f x
y >>= return ≡ return y, ∀ y
(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g
Эти законы обычно называются левой идентичностью, правильной идентичностью и составом, соответственно. Их можно более естественным образом выразить с точки зрения функции
(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
(f >=> g) x = (f x) >>= g
следующее:
return >=> f ≡ f, ∀ f
f >=> return ≡ f, ∀ f
(f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h
Lazy IO
Функции, выполняющие вычисления ввода-вывода, как правило, строгие, что означает, что все предшествующие действия в последовательности действий должны быть завершены до начала следующего действия. Обычно это полезное и ожидаемое поведение - putStrLn "X" >> putStrLn "Y"
должен печатать «XY». Однако некоторые функции библиотеки выполняют лёгкие операции ввода-вывода, что означает, что действия ввода-вывода, необходимые для получения значения, выполняются только тогда, когда фактически используется значение. Примерами таких функций являются getContents
и readFile
. Lazy I / O может резко снизить производительность программы Haskell, поэтому при использовании библиотечных функций следует обратить внимание на то, какие функции являются ленивыми.
IO и do
нотации
Haskell обеспечивает более простой способ объединения различных значений ввода-вывода в большие значения IO. Этот специальный синтаксис известен как do
обозначение * и это просто синтаксический сахар для использований в >>=
, >>
и return
функций.
Программа в предыдущем разделе , можно записать двумя различными способами , используя do
нотации, первый из которых расположение чувствительных и второе существо расположение нечувствительны:
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
main = do {
putStrLn "What is your name?" ;
name <- getLine ;
putStrLn ("Hello " ++ name ++ "!")
}
Все три программы в точности эквивалентны.
* Обратите внимание , что do
обозначение также применимо к более широкому классу конструкторов типа под названием монада.
Получение «a» из «IO a»
Общий вопрос : « У меня есть значение IO a
, но я хочу сделать что - то , что a
стоимость:? Как я могу получить доступ к нему» Как можно работать с данными, поступающими из внешнего мира (например, увеличивая число, набранное пользователем)?
Дело в том, что если вы используете чистую функцию для данных, полученных нечисто, тогда результат все еще нечист. Это зависит от того, что сделал пользователь! Значение типа IO a
стендах для «вычисления бокового осуществления , в результате чего значения типа a
» , который может быть запущен только (а) слагающим его в main
и (б) составление и выполнение программы. По этой причине, нет никакого способа , в чистом Haskell мире «получить a
аут».
Вместо этого мы хотим построить новое вычисление, новое значение IO
, которое использует значение a
во время выполнения . Это еще один способ составления значений IO
и поэтому мы можем использовать 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
Здесь мы используем чистую функцию ( getMessage
) , чтобы превратить Int
в String
, но мы используем do
запись , чтобы сделать его можно применять в результате IO
вычисления myComputation
когда (после) гласит , что вычисление. В результате получается более крупное вычисление IO
newComputation
. Этот метод использования чистых функций в нечистом контексте называется подъемом .
Письмо на stdout
В соответствии с спецификацией языка Haskell 2010 следующие стандартные функции ввода-вывода доступны в Prelude, поэтому импорт не требуется для их использования.
putChar :: Char -> IO ()
- записывает char
в stdout
Prelude> putChar 'a'
aPrelude> -- Note, no new line
putStr :: String -> IO ()
- записывает String
в stdout
Prelude> putStr "This is a string!"
This is a string!Prelude> -- Note, no new line
putStrLn :: String -> IO ()
- записывает String
в stdout
и добавляет новую строку
Prelude> putStrLn "Hi there, this is another String!"
Hi there, this is another String!
print :: Show a => a -> IO ()
- пишет a
экземпляр Show
на 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
Напомним, что вы можете создавать экземпляры Show
для ваших собственных типов, используя deriving
:
-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex") -- Person is an instance of `Show`, thanks to `deriving`
Загрузка и запуск в 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>
Чтение из `stdin`
В соответствии с спецификацией языка Haskell 2010 следующие стандартные функции ввода-вывода доступны в Prelude, поэтому импорт не требуется для их использования.
getChar :: IO Char
- читать Char
от 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
- чтение String
из stdin
, новый символ строки
Prelude> getLine
Hello there! -- user enters some text and presses enter
"Hello there!"
read :: Read a => String -> a
- преобразовать String в значение
Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True
Другими, менее распространенными функциями являются:
-
getContents :: IO String
- возвращает все входные данные пользователя как одну строку, которая читается лениво, поскольку это необходимо -
interact :: (String -> String) -> IO ()
- принимает в качестве аргумента функцию типа String-> String. Весь вход со стандартного устройства ввода передается этой функции в качестве аргумента