В этой главе мы изучили основы системы ввода-вывода языка Haskell. Также мы узнали, что такое действия ввода-вывода, как они позволяют выполнять ввод-вывод, в какой момент они выполняются. Итак, повторим пройденное: действия ввода-вывода – это значения, такие же, как любые другие в языке Haskell. Мы можем передать их в функции как параметры, функции могут возвращать действия ввода-вывода в качестве результата. Они отличаются тем, что если они попадут в функцию main
(или их введут в интерпретаторе GHCi), то будут выполнены. В этот момент они могут выводить что-либо на экран или управлять звуковыводящим устройством. Каждое действие ввода-вывода может содержать результат общения с реальным миром.
Не думайте о функции, например о putStrLn
, как о функции, которая принимает строку и печатает её на экране. Думайте о ней как о функции, которая принимает строку и возвращает действие ввода-вывода. Это действие при выполнении печатает нечто ценное на вашем терминале.
9
Больше ввода и вывода
Теперь, когда вы понимаете идеи, лежащие в основе ввода-вывода в языке Haskell, можно приступать к интересным штукам. В этой главе мы будем обрабатывать файлы, генерировать случайные числа, читать аргументы командной строки и много чего ещё. Будьте готовы!
Файлы и потоки
Вооружившись знанием того, как работают действия ввода-вывода, можно перейти к чтению и записи файлов. Но прежде давайте посмотрим, как Haskell умеет работать с потоками данных.
Перенаправление ввода
Многие интерактивные программы получают пользовательский ввод с клавиатуры. Однако зачастую гораздо удобнее «скормить» программе содержимое текстового файла. Такой способ подачи входных данных называется
Посмотрим, как перенаправление ввода работает с программой на языке Haskell. Для начала создадим текстовый файл, содержащий небольшое хайку, и сохраним его под именем
Я маленький чайник
Ох уж этот обед в самолёте
Он столь мал и невкусен
Ну да, хайку, прямо скажем, не шедевр – и что? Если кто в курсе, где найти хороший учебник по хайку, дайте знать.
Теперь напишем маленькую программу, которая непрерывно читает строку ввода и выводит её в верхнем регистре:
import Control.Monad
import Data.Char
main = forever $ do
l <- getLine
putStrLn $ map toUpper l
Сохраните эту программу в файле
Вместо того чтобы вводить строки с клавиатуры, мы перенаправим на вход программы содержимое файла <
после имени программы и затем указать имя файла, в котором хранятся исходные данные. Посмотрите:
$ ghc capslocker
[1 of 1] Compiling Main ( capslocker.hs, capslocker.o )
Linking capslocker ...
$ ./capslocker < haiku.txt
Я МАЛЕНЬКИЙ ЧАЙНИК
ОХ УЖ ЭТОТ ОБЕД В САМОЛЁТЕ
ОН СТОЛЬ МАЛ И НЕВКУСЕН
capslocker:
То, что мы проделали, практически эквивалентно запуску программы capslocker
, вводу нашего хайку с клавиатуры и передаче символа конца файла (обычно это делается нажатием клавиш Ctrl+D). С тем же успехом можно было бы запустить capslocker
и сказать: «Погоди, не читай ничего с клавиатуры, возьми содержимое этого файла!».
Получение строк из входного потока
Давайте посмотрим на действие ввода-вывода getContents
, упрощающее обработку входного потока за счёт того, что оно позволяет рассматривать весь поток как обычную строку. Действие getContents
читает всё содержимое стандартного потока ввода вплоть до обнаружения символа конца файла. Его тип: getContents :: IO String
. Самое приятное в этом действии то, что ввод-вывод в его исполнении является ленивым. Это означает, что выполнение foo <- getContents
не приводит к загрузке в память всего содержимого потока и связыванию его с именем foo
. Нет, действие getContents
для этого слишком лениво. Оно скажет: «Да, да, я прочту входные данные с терминала как-нибудь потом, когда это действительно понадобится!».