haskell-notes

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

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

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

на основе прочитанного текста. При этом в памяти будет храниться лишь малая часть файла. Но иногда

это свойство мешает. Рассмотрим такую задачу: перевернуть текст в файле под именем ”test”. Мы должны

сначала считать текст из файла, затем перевернуть его и в конце записать в тот же файл. Мы могли бы

написать эту программу так:

module Main where

main :: IO ()

main = inFile reverse ”test”

inFile :: (String -> String) -> FilePath -> IO ()

inFile fun file = writeFile file . fun =<< readFile file

Типичные задачи IO | 131

Функция inFile обновляет текст файла с помощью некоторого преобразование. Но если мы запустим эту

программу:

*Main> main

*** Exception: test: openFile: resource busy (file is locked)

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

ция readFile заняла файл, за счёт чтения по кусочкам. Для решения этой проблемы необходимо воспользо-

ваться энергичной версией функции readFile, она будет читать файл целиком. Эта функция живёт в модуле

System.IO.Strict:

import qualified System.IO.Strict as StrictIO

inFile :: (String -> String) -> FilePath -> IO ()

inFile fun file = writeFile file . fun =<< StrictIO. readFile file

Функция main осталась прежней. Теперь наша программа спокойно переворачивает текст файла.

Аргументы программы

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

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162