Поиск…


Чтение всего содержимого стандартного ввода в строку

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!
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. Весь вход со стандартного устройства ввода передается этой функции в качестве аргумента


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow