haskell-notes

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

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 осталась прежней. Теперь наша программа спокойно переворачивает текст файла.

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

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

программы, они работали в интерактивном режиме, но чаще всего программы принимают какие-нибудь

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

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.

Случайные значения

Функции для создания случайных значений определены в модуле System.Random. Модуль System.Random

входит в библиотеку random. Если в вашей поставке ghc его не оказалось, вы можете установить его вручную

через интернет, набрав в командной строке cabal install random. Сначала давайте разберёмся как гене-

рируются случайные числа. Стандартные случайные числа очень похожи на те, что были у нас, когда мы

рассматривали примеры специальных функций. У нас есть генератор случайных чисел типа g и с помощью

функции next мы можем получить обновлённый генератор и случайное целое число:

next :: g -> (Int, g)

Не правда ли этот тип очень похож на тип результата функций с состоянием. В качестве состояния теперь

выступает генератор случайных чисел g. Это поведение описывается классом RandomGen:

Читай продолжение на следующей странице
Добавить комментарии