но. Например мы можем читать содержание очень большого файла и составлять какую-нибудь статистику
на основе прочитанного текста. При этом в памяти будет храниться лишь малая часть файла. Но иногда
это свойство мешает. Рассмотрим такую задачу: перевернуть текст в файле под именем ”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 осталась прежней. Теперь наша программа спокойно переворачивает текст файла.
Аргументы программы
Пока программы, которые мы создавали просили пользователя ввести данные вручную при выполнении
программы, они работали в интерактивном режиме, но чаще всего программы принимают какие-нибудь
начальные данные, установки или флаги. Читать начальные данные можно с помощью функций из модуля
System.Environment.
Узнать, что передаётся в программу можно функцией getArgs :: IO [String]. Она возвращает список
строк. Это те строки, что мы написали за именем программы через пробел при вызове в терминале. Напишем
простую программу, которая распечатывает свои аргументы по порядку, в виде пронумерованного списка.
module Main where
import System.Environment
main = getArgs >>= mapM_ putStrLn . zipWith f [1 .. ]
where f n a = show n ++ ”: ” ++ a
В локальной функции f мы присоединяем к строке номер через двоеточие. Функцией mapM_ мы пробегаем
по списку строк, отображая их с помощью функции putStrLn. Обратите внимание на краткость программы,
с помощью функции композиции мы легко составили функцию, которая приписывает к аргументам числа, а
затем выводит их на экран.
Скомпилируем программу в интерпретаторе и вызовем её.
*Main> :! ghc --make Args
[1 of 1] Compiling Main
( Args. hs, Args. o )
Linking Args ...
*Main> :! ./Args hey hey hey 23 54 ”qwe qwe qwe” fin
1: hey
2: hey
3: hey
4: 23
5: 54
6: qwe qwe qwe
7: fin
Если мы хотим, чтобы аргумент-строка содержал пробелы мы заключаем его в двойные кавычки.
С помощью функции getProgName можно узнать имя программы. Создадим программу, которая здоро-
вается при вызове. И отвечает в зависимости от настроения программы. Настроение задаётся аргументом
программы.
module Main where
import Control.Applicative
import System.Environment
main = putStrLn =<< reply <$> getProgName <*> getArgs
132 | Глава 8: IO
reply :: String -> [String] -> String
reply name (x:_) = hi name ++ case x of
”happy”
-> ”What a lovely day. What’s up?”
”sad”
-> ”Ooohh. Have you got some news for me?”
”neutral”
-> ”How are you?”
reply name _
= reply name [”neutral”]
hi :: String -> String
hi name = ”Hi! My name is ” ++ name ++ ”.\n”
В функции reply мы составляем реплику программы. Она зависит от имени программы и поступающих
на вход аргументов. Посмотрим, что у нас получилось:
*Main> :! ghc --make HowAreYou.hs -o ninja
[1 of 1] Compiling Main
( HowAreYou. hs, HowAreYou. o )
Linking ninja ...
*Main> :! ./ninja happy
Hi! My name is ninja.
What a lovely day. What’s up?
*Main> :! ./ninja sad
Hi! My name is ninja.
Ooohh. Have you got some news for me?
Вызов других программ
Мы можем вызвать любую программу из нашей программы. Это делается с помощью функции system,
которая живёт в модуле System.
system :: String -> IO ExitCode
Она принимает строку и запускает её в терминале. Так же как мы делали это с помощью приставки :! в
интерпретаторе. Значение типа ExitCode говорит о результате выполнения строки. Он может быть успешным,
тогда функция вернёт ExitSuccess и закончиться ошибкой, тогда мы сможем узнать код ошибки по значению
ExitFailure Int.
Случайные значения