Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Использование функции withFileСодержание книги
Поиск на нашем сайте
То, что мы только что сделали, можно сделать и по-другому – с ис- пользованием функции withFile. Сигнатура этой функции: withFile:: FilePath –> IOMode –> (Handle –> IO a) –> IO a Она принимает путь к файлу, режим открытия файла и некото- рую функцию, принимающую дескриптор и возвращающую некое действие ввода-вывода. Функция withFile вернёт действие ввода- вывода, которое откроет файл, сделает с ним то, что нам нужно, и закроет его. Результат, помещённый в заключительном действии ввода-вывода, будет взят из результата переданной нами функции. С виду это может показаться сложным, но на самом деле всё просто, особенно если использовать анонимные функции. Вот как можно пе- реписать предыдущий пример с использованием функции withFile: import System.IO main = do withFile "girlfriend.txt" ReadMode (\handle –> do contents <– hGetContents handle putStr contents)
Функция (\handle -> …) принимает дескриптор файла и возвра- щает действие ввода-вывода. Обычно пишут именно так, пользу- ясь анонимной функцией. Нам действительно нужна функция, возвращающая действие ввода-вывода, а не просто выполнение некоторого действия и последующее закрытие файла, посколь- ку действие, переданное функции withFile, не знало бы, с каким файлом ему необходимо работать. Сейчас же функция withFile открывает файл, а затем передаёт его дескриптор функции, кото- рую мы ей передали. Функция возвращает действие ввода-вывода, на основе которого withFile создаёт новое действие, работающее почти так же, как и исходное, но с добавлением гарантированно- го закрытия файла даже в тех случаях, когда что-то пошло не так.
Время заключать в скобки Обычно, если какой-нибудь фрагмент кода вызывает функцию error (например, когда мы пытаемся вызвать функцию head для пустого списка) или случается что-то плохое при вводе-выво- де, наша программа завершается с сообщениемобошибке. Втаких обстоятельствах говорят, что произошло исключение. Функция withFile гарантирует, что незави- симо от того, возникнет исключе- ние или нет, файл будет закрыт. Подобные сценарии встреча- ются довольно часто. Мы получа- ем в распоряжение некоторый ресурс (например, файловый дескриптор), хотим с ним что-ни- будь сделать, но кроме того хотим, чтобы он был освобождён (файл закрыт). Как раз для таких слу- чаев в модуле Control.Exception имеется функция bracket. Вот её сигнатура: bracket:: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
Первым параметром является действие, получающее ресурс (дескриптор файла). Второй параметр – функция, освобождающая ресурс. Эта функция будет вызвана даже в случае возникновения исключения. Третий параметр – это функция, которая также при- нимает на вход ресурс и что-то с ним делает. Именно в третьем па- раметре и происходит всё самое важное, а именно: чтение файла или его запись. Поскольку функция bracket – это и есть всё необходимое для получения ресурса, работы с ним и гарантированного освобожде- ния, с её помощью можно получить простую реализацию функции withFile: withFile:: FilePath –> IOMode –> (Handle –> IO a) –> IO a withFile name mode f = bracket (openFile name mode) (\handle -> hClose handle) (\handle -> f handle) Первый параметр, который мы передали функции bracket, от- крывает файл; результатом являетсядескриптор. Второйпараметр принимает дескриптор и закрывает его. Функция bracket даёт га- рантию, что это произойдёт, даже если возникнет исключение. На- конец, третий параметр функции bracket принимает дескриптор и применяет к нему функцию f, которая по заданному дескриптору делает с файлом всё необходимое, будь то его чтение или запись.
Хватай дескрипторы! Подобно тому как функция 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, и дейс- твует почти так же. Она только не обрезает уже существующий файл до нулевой длины перед записью, а добавляет новое содержимое в конец файла.
Список дел Воспользуемся функцией appendFile на примере написания про- граммы, которая добавляет в текстовый файл, содержащий список наших дел, новое задание. Допустим, у нас уже есть такой файл с на- званием todo.txt, и каждая его строка соответствует одному заданию. Наша программа будет читать из стандартного потока ввода одну строку и добавлять её в конец файла todo.txt: import System.IO
main = do todoItem <– getLine appendFile "todo.txt" (todoItem ++ "\n") Обратите внимание на добавление символа конца строки вруч- ную, функция getLine возвращает строку без него. Сохраните этот файл с именем appendtodo.hs, скомпилируйте его и несколько раз запустите. $./appendtodo Погладить посуду $./appendtodo Помыть собаку $./appendtodo Вынуть салат из печи $ cat todo.txt Погладить посуду Помыть собаку Вынуть салат из печи
ПРИМЕЧАНИЕ. Программа cat в Unix-подобных системах исполь- зуется для вывода содержимого текстового файла на терминал. В Windows можно воспользоваться командой type или посмотреть содержимое файла в любом текстовом редакторе. Удаление заданий Мы уже написали программу, которая добавляет новый элемент к списку заданий в файл todo.txt; теперь напишем программу для уда- ления элемента. Мы применим несколько новых функций из моду- ля System.Directory и одну новую функцию из модуля System.IO; их работа будет объяснена позднее. import System.IO import System.Directory import Data.List
main = do contents <– readFile "todo.txt" let todoTasks = lines contents numberedTasks = zipWith (\n line –> show n ++ " – " ++ line) [0..] todoTasks putStrLn "Ваши задания:" mapM_ putStrLn numberedTasks putStrLn "Что вы хотите удалить?" numberString <– getLine let number = read numberString newTodoItems = unlines $ delete (todoTasks!! number) todoTasks (tempName, tempHandle) <– openTempFile "." "temp" hPutStr tempHandle newTodoItems hClose tempHandle removeFile "todo.txt" renameFile tempName "todo.txt" Сначала мы читаем содержимое файла todo.txt и связываем его с именем contents. Затем разбиваем всё содержимое на список строк. Список todoTasks выглядит примерно так: ["Погладить посуду", "Помыть собаку", "Вынуть салат из печи"] Далее соединяем числа, начиная с 0, и элементы списка дел с помощью функции, которая берёт число (скажем, 3) и строку (на- пример, "привет") и возвращает новую строку ("3 – привет"). Вот примерный вид списка numberedTasks: ["0 - Погладить посуду", "1 - Помыть собаку", "2 - Вынуть салат из печи"]
Затем с помощью вызова mapM_ putStrLn numberedTasks мы печа- таем каждое задание на отдельной строке, после чего спрашиваем пользователя, что он хочет удалить, и ждём его ответа. Например, он хочет удалить задание 1 (Помыть собаку), так что мы получим число 1. Значением переменной numberString будет "1", и, посколь- ку вместо строки нам необходимо число, мы применяем функцию read и связываем результат с именем number. Помните функции delete и!! из модуля Data.List? Оператор!! возвращает элемент из списка по индексу, функция delete удаляет первое вхождение элемента в список, возвращая новый список без удалённого элемента. Выражение (todoTasks!! number), где number – это 1, возвращает строку "Помыть собаку". Мы удаляем первое вхож- дение этой строки из списка todoTasks, собираем всё оставшееся в одну строку функцией unlines и даём результату имя newTodoItems. Далее используем новую функцию из модуля System.IO – openTempFile. Имя функции говорит само за себя: open temp file – «открыть временный файл». Она принимает путь к временному каталогу и шаблон имени файла и открывает временный файл. Мы использовали символ. в качестве каталога для временных файлов, так как. обозначает текущий каталог практически во всех операци- онных системах. Строку "temp" мы указали в качестве шаблона имени для временного файла; это означает, что временный файл будет на- зван temp плюс несколько случайных символов. Функция возвращает действие ввода-вывода, которое создаст временный файл; результат действия – пара значений, имя временного файла и дескриптор. Мы могли бы открыть обычный файл, например с именем todo2.txt, но использовать openTempFile – хорошая практика: в этом случае не при- ходится опасаться, что вы случайно что-нибудь перезапишете. Теперь, когда временный файл открыт, запишем туда строку newTodoItems. В этот момент исходный файл не изменён, а времен- ный содержит все строки из исходного, за исключением удалённой. Затем мы закрываем временный файл и удаляем исходный с помощью функции removeFile, которая принимает путь к файлу и удаляет его. После удаления старого файла todo.txt мы использу- ем функцию renameFile, чтобы переименовать временный файл в todo.txt. Обратите внимание: функции removeFile и renameFile (обе они определены в модуле System.Directory) принимают в качестве параметров не дескрипторы, а пути к файлам.
Сохраните программу в файле с именем deletetodo.hs, скомпили- руйте её и проверьте: $./deletetodo Ваши задания: 0 – Погладить посуду 1 – Помыть собаку 2 – Вынуть салат из печи Что вы хотите удалить? 1 Смотрим, что осталось: $ cat todo.txt Погладить посуду Вынуть салат из печи Круто! Удалим ещё что-нибудь: $./deletetodo Ваши задания: 0 – Погладить посуду 1 – Вынуть салат из печи Что вы хотите удалить? 0 Проверяя файл с заданиями, убеждаемся, что осталось только одно: $ cat todo.txt Вынуть салат из печи Итак, всё работает. Осталась только одна вещь, которую мы в этой программе не учли. Если после открытия временного файла что-то произойдёт и программа неожиданно завершится, то вре- менный файл не будет удалён. Давайте это исправим.
Уборка Чтобы гарантировать удаление временного файла, воспользуемся функцией bracketOnError из модуля Control.Exception. Она очень по- хожа на bracket, но если последняя получает ресурс и гарантирует, что освобождение ресурса будет выполнено всегда, то функция bracketOnError выполнит завершающие действия только в случае возникновения исключения. Вот исправленный код: АРГУМЕНТЫ КОМАНДНОЙ СТРОКИ 241 import System.IO import System.Directory import Data.List import Control.Exception
main = do contents <– readFile "todo.txt" let todoTasks = lines contents numberedTasks = zipWith (\n line –> show n ++ " – " ++ line) [0..] todoTasks putStrLn "Ваши задания:" mapM_ putStrLn numberedTasks putStrLn "Что вы хотите удалить?" numberString <– getLine let number = read numberString newTodoItems = unlines $ delete (todoTasks!! number) todoTasks bracketOnError (openTempFile "." "temp") (\(tempName, tempHandle) –> do hClose tempHandle removeFile tempName) (\(tempName, tempHandle) –> do hPutStr tempHandle newTodoItems hClose tempHandle removeFile "todo.txt" renameFile tempName "todo.txt") Вместо обычного использования функции openTempFile мы за- ключаем её в bracketOnError. Затем пишем, что должно произойти при возникновении исключения: мы хотим закрыть и удалить вре- менный файл. Если же всё нормально, пишем новый список зада- ний во временный файл; все эти строки остались без изменения. Мы выводим новые задания, удаляем исходный файл и переимено- вываем временный.
Аргументы командной строки Если вы пишете консольный скрипт или приложение, то вам на- верняка понадобится работать с аргументами командной строки. К счастью, в стандартную библиотеку языка Haskell входят удоб- ные функции для работы с ними. В предыдущей главе мы написали программы для добавления и удаления элемента в список заданий. Но у нашего подхода есть две
проблемы. Во-первых, мы жёстко задали имя файла со списком заданий в текс- те программы. Мы решили, что файл будет называться todo.txt, и что пользователь никогда не захочет вести несколько списков. Эту проблему можно решить, спрашивая поль- зователя каждый раз, какой файл он хочет ис- пользовать как файл со списком заданий. Мы использовали такой подход, когда спраши- вали пользователя, какой элемент он хочет удалить. Это, конеч- но, работает, но не идеально, поскольку пользователь должен запустить программу, подождать, пока она спросит что-нибудь, и затем дать ответ. Такая программа называется интерактивной, и сложность здесь заключается вот в чём: вдруг вам понадобится автоматизировать выполнение этой программы, например, с по- мощью скрипта? Гораздо сложнее написать скрипт, который будет взаимодействовать с программой, чем обычный скрипт, который просто вызовет её один или несколько раз! Вот почему иногда лучше сделать так, чтобы пользователь сооб- щал, чего он хочет, при запуске программы, вместо того чтобы она сама спрашивала его после запуска. И что может послужить этой цели лучше командной строки!.. В модуле System.Environment есть два полезных действия ввода- вывода. Первое – это функция getArgs; её тип – getArgs:: IO [String]. Она получает аргументы, с которыми была вызвана программа, и возвращает их в виде списка. Второе – функция getProgName, тип которой – getProgName:: IO String. Это действие ввода-вывода, воз- вращающее имя программы. Вот простенькая программа, которая показывает, как работают эти два действия: import System.Environment import Data.List
main = do
args <– getArgs progName <– getProgName putStrLn "Аргументы командной строки:" mapM putStrLn args putStrLn "Имя программы:" putStrLn progName Мы связываем значения, возвращаемые функциями getArgs и progName, с именами args и progName. Выводим строку "Аргументы командной строки:" и затем для каждого аргумента из списка args вы- полняем функцию putStrLn. После этого печатаем имя программы. Скомпилируем программу с именем arg-test и проверим, как она работает: $./arg-test first second w00t "multi word arg" Аргументы командной строки: first second w00t multi word arg Имя программы: arg-test
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2017-02-17; просмотров: 178; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.221.157.203 (0.008 с.) |