вычисления ей нужны дополнительные данные от пользователя, на основе которых мы сможем продолжить
вычисления с помощью какой-нибудь другой функции. Тогда наша программа примет вид:
program =
liftA2 algorithm2 readInit
(liftA2 algorithm1 readInit (readConfig ”file”))
— функции с побочными эффектами
readInit
:: IO Int
readConfig :: String -> IO Config
:: Show a => a -> IO ()
— большие и сложные, но !чистые! функции
algorithm1
:: Int -> Config -> Result1
algorithm2
:: Int -> Result1 -> Result2
Теперь у нас два кадра, программа выполняется в два этапа. Каждый из них разделён участками взаимо-
действия с пользователем. Но тип IO присутствует лишь в первых шести строчках, остальные два миллиона
строк написаны в мире описаний, исключительно чистыми функциями, которые поднимаются в мир специ-
альных функций с помощью функций liftA2 и стыкуются с помощью операции связывания >>=.
Попробуем тип IO в интерпретаторе. Мы будем пользоваться двумя стандартными функциями getChar и
— читает символ с клавиатуры
getChar :: IO Char
— выводит значение на экран
print :: IO ()
128 | Глава 8: IO
Функция print возвращает значение единичного типа, завёрнутое в тип IO, поскольку нас интересует не
само значение а побочный эффект, который выполняет эта функция, в данном случае это вывод на экран.
Закодируем два примера из первого раздела. В первом мы читаем один символ и печатаем его дважды:
Prelude> :m Control.Applicative
Prelude Control.Applicative> let res = (c -> c:c:[]) getChar >>= print
Prelude Control.Applicative> res
q”qq”
Мы сначала вызываем функцию getChar удваиваем результат функцией c -> c:c:[] и затем выводим
на экран.
Во втором примере мы дважды запрашиваем символ с клавиатуры а затем печатаем их:
Prelude Control.Applicative> let res = liftA2 (a b -> a:b:[]) getChar getChar >>= print
Prelude Control.Applicative> res
qw”qw”
8.3 Как пишутся программы
Мы уже умеем читать с клавиатуры и выводить значения на экран. Давайте научимся писать самостоя-
тельные программы. Программа обозначается специальным именем:
main :: IO ()
Если модуль называется Main или в нём нет директивы module … where и в модуле есть функция main
:: IO (), то после компиляции будет сделан исполняемый файл. Его можно запускать независимо от ghci.
Просто нажимаем дважды мышкой или вызываем из командной строки.
Напишем программу Hello world. Единственное, что она делает это выводит на экран приветствие:
main :: IO ()
main = print ”Hello World!”
Теперь сохраним эти строчки в файле Hello. hs, перейдём в директорию файла и скомпилируем файл:
ghc —make Hello
Появились объектный и интерфейсный файлы, а также появился третий бинарный файл. Это либо Hello
без расширения (в Linux) или Hello. exe (в Windows). Запустим этот файл:
$ ./Hello
”Hello World!”
Получилось! Это наша первая программа. Теперь напишем программу, которая принимает три символа
с клавиатуры и выводит их в обратном порядке:
import Control.Applicative
f :: Char -> Char -> Char -> String
f a b c = reverse $ [a,b,c]
main :: IO ()
main = print =<< f getChar getChar getChar
Сохраним в файле ReverseIO. hs и скомпилируем:
ghc —make ReverseIO -o rev3
Дополнительным флагом —o мы попросили компилятор чтобы он сохранил исполняемый файл под име-
нем rev3. Теперь запустим в командной строке:
$ ./rev3
qwe
”ewq”
Как пишутся программы | 129
Набираем три символа и нажимаем ввод. И программа переворачивает ответ. Обратите внимание на то,
что с помощью print мы выводим не просто строку на экран, а строку как значение. Поэтому добавляются
двойные кавычки. Для того чтобы выводить строку существует функция putStr. Заменим print на putStr,
перекомпилируем и посмотрим что получится:
$ ghc —make ReverseIOstr -o rev3str
[1 of 1] Compiling Main
( ReverseIOstr.hs, ReverseIOstr.o )
Linking rev3str …
$ ./rev3str
123
321$
Видно, что после вывода не произошёл перенос каретки, терминал приглашает нас к вводу команды сразу
за ответом, если перенос нужен, можно воспользоваться функцией putStrLn. Обратите внимание на то, что
кроме бинарного файла появились ещё два файла с расширениями . hi и . o. Первый файл называется ин-
терфейсным он описывает какие в модуле определения, а второй файл называется объектным. Он содержит
скомпилированный код модуля.
Стоит отметить команду runhaskell. Она запускает программу без создания дополнительных файлов.
Но в этом случае выполнение программы будет происходить медленнее.
8.4 Типичные задачи IO
Вывод на экран
Нам уже встретилось несколько функций вывода на экран. Это функции: print (вывод значения из эк-
земпляра класса Show), putStr (вывод строки) и putStrLn (вывод строки с переносом). Каждый раз когда мы
набираем какое-нибудь выражение в строке интерпретатора и нажимаем Enter, интерпретатор применяет к
выражению функцию print и мы видим его на экране.
Из простейших функций вывода на экран осталось не рассмотренной лишь функция putChar, но я думаю
вы без труда догадаетесь по типу и имени чем она занимается:
putChar :: Char -> IO ()
Функции вывода на экран также можно вызывать в интерпретаторе: