Хватай дескрипторы!
Подобно тому как функция hGetContents работает по аналогии с функцией getContents, но с указанным файлом, существуют функции hGetLine, hPutStr, hPutStrLn, hGetChar и т. д., ведущие себя так же, как их варианты без буквы h, но принимающие дескриптор как параметр и работающие с файлом, а не со стандартным вводом-выводом. Пример: putStrLn – это функция, принимающая строку и возвращающая действие ввода-вывода, которое напечатает строку на терминале, а затем выполнит перевод на новую строку. Функция hPutStrLn принимает дескриптор файла и строку и возвращает действие, которое запишет строку в файл и затем поместит в файл символ(ы) перехода на новую строку. Функция hGetLine принимает дескриптор и возвращает действие, которое считывает строку из файла.
Загрузка файлов и обработка их содержимого в виде строк настолько распространена, что есть три маленькие удобные функции, которые делают эту задачу ещё легче.
Сигнатура функции readFile такова:
readFile :: FilePath –> IO String
Мы помним, что тип FilePath – это просто удобное обозначение для String. Функция readFile принимает путь к файлу и возвращает действие ввода-вывода, которое прочитает файл (лениво, конечно же) и свяжет содержимое файла в виде строки с некоторым именем. Обычно это более удобно, чем вызывать функцию openFile и связывать дескриптор с именем, а затем вызывать функцию hGetContents. Вот как мы могли бы переписать предыдущий пример с использованием readFile:
import System.IO
main = do
contents <– readFile "girlfriend.txt"
putStr contents
Так как мы не получаем дескриптор файла в качестве результата, то не можем закрыть его сами. Если мы используем функцию readFile, за нас это сделает язык Haskell.
Функция writeFile имеет тип
writeFile :: FilePath –> String –> IO ()
Она принимает путь к файлу и строку для записи в файл и возвращает действие ввода-вывода, которое выполнит запись. Если такой файл уже существует, перед записью он будет обрезан до нулевой длины. Вот как получить версию файла girlfriend.txt в верхнем регистре и записать её в файл girlfriendcaps.txt:
import System.IO
import Data.Char
main = do
contents <– readFile "girlfriend.txt"
writeFile "girlfriendcaps.txt" (map toUpper contents)
Функция appendFile имеет ту же сигнатуру, что и writeFile, и действует почти так же. Она только не обрезает уже существующий файл до нулевой длины перед записью, а добавляет новое содержимое в конец файла.