Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву
Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Использование функции withFileСодержание книги
Поиск на нашем сайте
То, что мы только что сделали, можно сделать и по-другому – с ис- пользованием функции withFile. Сигнатура этой функции: withFile:: FilePath –> IOMode –> (Handle –> IO a) –> IO a
Она принимает путь к файлу, режим открытия файла и некото- рую функцию, принимающую дескриптор и возвращающую некое действие ввода-вывода. Функция withFile вернёт действие ввода- вывода, которое откроет файл, сделает с ним то, что нам нужно, и закроет его. Результат, помещённый в заключительном действии ввода-вывода, будет взят из результата переданной нами функции. С виду это может показаться сложным, но на самом деле всё просто, особенно если использовать анонимные функции. Вот как можно пе- реписать предыдущий пример с использованием функции withFile:
withFile "girlfriend.txt" ReadMode (\handle –> do contents <– hGetContents handle
putStr contents)
Функция (\handle -> …) принимает дескриптор файла и возвра- щает действие ввода-вывода. Обычно пишут именно так, пользу- ясь анонимной функцией. Нам действительно нужна функция, возвращающая действие ввода-вывода, а не просто выполнение некоторого действия и последующее закрытие файла, посколь- ку действие, переданное функции withFile, не знало бы, с каким файлом ему необходимо работать. Сейчас же функция withFile открывает файл, а затем передаёт его дескриптор функции, кото- рую мы ей передали. Функция возвращает действие ввода-вывода, на основе которого withFile создаёт новое действие, работающее почти так же, как и исходное, но с добавлением гарантированно- го закрытия файла даже в тех случаях, когда что-то пошло не так.
Время заключать в скобки Обычно, если какой-нибудь фрагмент кода вызывает функцию error (например, когда мы пытаемся вызвать функцию head для
сигнатура: 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 входят удоб- ные функции для работы с ними. В предыдущей главе мы написали программы для добавления и удаления элемента в список заданий. Но у нашего подхода есть две
Эту проблему можно решить, спрашивая поль- зователя каждый раз, какой файл он хочет ис- пользовать как файл со списком заданий. Мы использовали такой подход, когда спраши- вали пользователя, какой элемент он хочет удалить. Это, конеч- но, работает, но не идеально, поскольку пользователь должен запустить программу, подождать, пока она спросит что-нибудь, и затем дать ответ. Такая программа называется интерактивной, и сложность здесь заключается вот в чём: вдруг вам понадобится автоматизировать выполнение этой программы, например, с по- мощью скрипта? Гораздо сложнее написать скрипт, который будет взаимодействовать с программой, чем обычный скрипт, который просто вызовет её один или несколько раз! Вот почему иногда лучше сделать так, чтобы пользователь сооб- щал, чего он хочет, при запуске программы, вместо того чтобы она сама спрашивала его после запуска. И что может послужить этой цели лучше командной строки!.. В модуле 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; просмотров: 236; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 216.73.216.20 (0.009 с.) |