Выбрать главу

Нам уже встретилось несколько функций вывода на экран. Это функции: print (вывод значения из эк-

земпляра класса Show), putStr (вывод строки) и putStrLn (вывод строки с переносом). Каждый раз когда мы

набираем какое-нибудь выражение в строке интерпретатора и нажимаем Enter, интерпретатор применяет к

выражению функцию print и мы видим его на экране.

Из простейших функций вывода на экран осталось не рассмотренной лишь функция putChar, но я думаю

вы без труда догадаетесь по типу и имени чем она занимается:

putChar :: Char -> IO ()

Функции вывода на экран также можно вызывать в интерпретаторе:

Prelude> putStr ”Hello” >> putChar ’ ’ >> putStrLn ”World!”

Hello World!

Обратите внимание на применение постоянной функции для монад >> . В этом выражении нас интересует

не результат, а те побочные эффекты, которые выполняются при композиции специальных функций. Также

мы пользовались функцией >> в сочетании с монадой Writer для накопления результата.

Ввод пользователя

Мы уже умеем принимать от пользователя буквы. Это делается функцией getChar. Функцией getLine мы

можем прочитать целую строчку. Строка читается до тех пор пока мы не нажмём Enter.

Prelude> fmap reverse $ getLine

Hello-hello!

”!olleh-olleH”

Есть ещё одна функция для чтения строк, она называется getContents. Основное отличие от getLine

заключается в том, что содержание не читается сразу, а откладывается на потом, когда содержание дей-

ствительно понадобится. Это ленивый ввод. Для задачи чтения символов с терминала эта функция может

показаться странной. Но часто в символы вводятся не вручную, а передаются из другого файла. Например

если мы направим на ввод данные из-какого-нибудь большого-большого файла, файл не будет читаться сра-

зу, и память не будет заполнена не нужным пока содержанием. Вместо этого программа отложит считывание

на потом и будет заниматься им лишь тогда, когда оно понадобится в вычислениях. Это может существенно

снизить расход памяти. Мы читаем файл в 2Гб моментально (мы делаем вид, что читаем его). А на самом

деле сохраняем себе задачу на будущее: читать ввод, когда придёт пора.

130 | Глава 8: IO

Чтение и запись файлов

Для чтения и записи файлов есть три простые функции:

type FilePath = String

-- чтение файла

readFile

:: FilePath -> IO String

-- запись строки в файл

writeFile

:: FilePath -> String -> IO ()

-- добавление строки в конеци файла

appendFile

:: FilePath -> String -> IO ()

Напишем программу, которая сначала запрашивает путь к файлу. Затем показывает его содержание. За-

тем запрашивает ввод строки из терминала. А после этого добавляет текст в конец файла.

main = msg1 >> getLine >>= read >>= append

where read

file = readFile file >>= putStrLn >> return file

append file = msg2 >> getLine >>= appendFile file

msg1

= putStr ”input file: ”

msg2

= putStr ”input text: ”

В самом левом вызове getLine мы читаем имя файла, затем оно используется в локальной функции

read. Там мы читаем содержание файла (readLine), выводим его на экран (putStrLn), и в самом конце мы

возвращаем из функции имя файла. Оно нам понадобится в следующей части программы, в которой мы

будем читать новые записи и добавлять их в файл. Новая запись читается функцией getLine в локальной

функции append.

Сохраним в модуле File. hs и посмотрим, что у нас получилось. Перед этим создадим в текущей дирек-

тории тестовый пустой файл под именем test. В него мы будем добавлять новые записи.

*Prelude> :l File

[1 of 1] Compiling File

( File. hs, interpreted )

Ok, modules loaded: File.

*File> main

input file: test

input text: Hello!

*File> main

input file: test

Hello!

input text: Hi)

*File> main

input file: test

Hello!Hi)

В самом начале наш файл пуст, поэтому сначала мы видим пустую строчку вместо содержания, но потом

мы начинаем добавлять в него новые записи.

Ленивое и энергичное чтение файлов

С чтением файлов связана одна тонкость. Функция readFile читает содержимое файла в ленивом стиле.

Подробнее о ленивой стратегии вычислений мы поговорим в следующей главе. По ка отметим, что readFile

не читает следующую порцию файла до тех пор пока она не понадобится в программе. Иногда это очень удоб-

но. Например мы можем читать содержание очень большого файла и составлять какую-нибудь статистику