ТОП 10:

Копирование файлов при помощи Bytestring



 
 

Давайте напишем простую программу, которая принимает два име- ни файла в командной строке и копирует первый файл во второй. Обратите внимание, что модуль System.Directory уже содержит функцию copyFile, но мы собираемся создать нашу собственную реализацию.

import System.Environment

import qualified Data.ByteString.Lazy as B


 

main = do

(fileName1:fileName2:_) <– getArgs copy fileName1 fileName2

 

copy :: FilePath –> FilePath –> IO () copy source dest = do

contents <– B.readFile source bracketOnError

(openTemplFile "." "temp") (\(tempName, tempHandle) -> do

hClose templHandle removeFile tempName)

(\(tempName, tempHandle) -> do B.hPutStr tempHandle contents hClose tempHandle

 
 

renameFile tempName dest)

В функции main мы получаем аргументы командной строки и вызываем функцию copy, в которой всё волшебство и происходит. Вообще говоря, можно было бы просто прочитать содержимое од- ного файла и записать его в другой. Однако если бы что-то пошло не так (например, закончилось бы место на диске), у нас в катало- ге остался бы файл с некорректным содержимым. Поэтому мы пи- шем во временный файл, который в случае возникновения ошибки просто удаляется.

Сначала для чтения содержимого входного файла мы исполь- зуем функцию B.readFile. Затем с помощью bracketOnError органи- зуем обработку ошибок. Мы получаем ресурс посредством вызова openTemplFile "." "temp", который возвращает пару из имени времен- ного файла и его дескриптора. После этого указываем, что должно произойти при возникновении исключения. В этом случае мы закро- ем дескриптор и удалим временный файл. Наконец, выполняется собственно копирование. Для записи содержимого во временный файл используется функция B.hPutStr. Временный файл закрывает- ся, и ему даётся имя, которое он должен иметь в итоге.

Заметьте, что мы использовали B.readFile и B.hPutStr вместо их обычных версий. Для открытия, закрытия и переименования фай- лов специальные функции не требуются. Они нужны только для чте- ния и записи.

Проверим программу:.


 

 
 

$ ./bytestringcopy bart.txt bort.txt

Обратите внимание, что программа, не использующая стро- ки байтов, могла бы выглядеть точно так же. Единственное отли- чие – то, что мы используем B.readFile и B.hPutStr вместо readFile и hPutStr. Во многих случаях вы можете «переориентировать» про- грамму, использующую обычные строки, на использование строк байтов, просто импортировав нужные модули и проставив имя мо- дуля перед некоторыми функциями. В ряде случаев вам придётся конвертировать свои собственные функции для использования строк байтов, но это несложно.

Если вы хотите улучшить производительность программы, которая считывает много данных в строки, попробуйте исполь- зовать строки байтов; скорее всего, вы добьётесь значительного улучшения производительности, затратив совсем немного усилий. Обычно я пишу программы, используя обычные строки, а затем переделываю их на использование строк байтов, если производи- тельность меня не устраивает.

 

Исключения1

В любой программе может встретиться фрагмент, который мо- жет отработать неправильно. Разные языки предлагают различ- ные способы обработки подобных ошибок. В языке С мы обычно используем некоторое заведомо неправильное возвращаемое значение (например, –1 или пустой указатель), чтобы указать, что результат функции не должен рассматриваться как правиль- ное значение. Языки Java и С#, с другой стороны, предлагают ис- пользовать для обработки ошибок механизм исключений. Когда возникает исключительная ситуация, выполнение программы передаётся некоему определённому нами участку кода, который выполняет ряд действий по восстановлению и, возможно, снова вызывает исключение, чтобы другой код для обработки ошибок мог выполниться и позаботиться о каких-либо других вещах.

 
 

В языке Haskell очень хорошая система типов. Алгебраические типы данных позволяют объявить такие типы данных, как Maybe и Either; мы можем использовать значения этих типов для представ- ления результатов, которые могут отсутствовать. В языке С выбор,

1 Текст этого раздела переработан в соответствии с современным стилем обработ- ки исключений. – Прим. ред.


скажем, –1 для сигнала об ошибке – это просто предварительная договоренность. Эта константа имеет значение только для челове- ка. Если мы не очень аккуратны, то можем трактовать подобные специальные значения как допустимые, и затем они могут приве- сти к упадку и разорению вашего кода. Система типов языка Haskell даёт нам столь желанную безопасность в этом аспекте. Функция a –> Maybe b явно указывает, что результатом может быть значение типа b, завёрнутое в конструктор Just, или значение Nothing. Тип функ- ции отличается от простого a –> b, и если мы попытаемся использо- вать один тип вместо другого, компилятор будет «жаловаться» на нас.

Кроме алгебраических типов, хорошо представляющих вычис- ления, которые могут закончиться неудачей, язык Haskell имеет поддержку исключительных ситуаций, так как они приобретают особое значение в контексте ввода-вывода. Всё может пойти вкривь и вкось, если вы работаете с внешним миром, который столь нена- дёжен! Например, при открытии файла может случиться всякое. Он может быть заблокирован, его может не оказаться по заданному пути, или не будет такого диска, или ещё что-нибудь...

 
 

При возникновении исключительной ситуации хорошо бы иметь возможность перейти на некоторый код обработки ошибки. Хоро- шо, код для ввода-вывода (то есть «грязный» код) может вызывать исключения. Имеет смысл. Ну а как насчёт чистого кода? Он тоже может вызывать исключения! Вспомним функции div и head. Их типы – (Integral a) => a –> a –> a и [a] –> a соответственно. Никаких значений типа Maybe или Either в возвращаемом типе, и тем не менее они могут вызвать ошибку! Функция div взорвётся у вас в руках, если вы попытаетесь разделить на нуль, а функция head выпадет в осадок, если передать ей пустой список.

ghci> 4 `div` 0

*** Exception: divide by zero ghci> head []

 
 

*** Exception: Prelude.head: empty list

Чистый код может выбрасывать исключения, но они могут быть перехвачены только в части кода, работающей с системой ввода-вы- вода (когда мы внутри блока do в функции main). Причина в том, что вы не знаете, когда что-то будет (если вообще будет!) вычислено в чистом коде, так как он ленив и не имеет жёстко определённого по-


 

рядка выполнения, в то время как код для ввода-вывода такой поря- док имеет.

Раньше мы говорили, что нам желательно проводить как мож- но меньше времени в части нашей программы, посвящённой вво- ду-выводу. Логика программы должна располагаться главным об- разом в чистых функциях, поскольку их результат зависит только от параметров, с которыми функции были вызваны. При работе с чистыми функциями вы должны думать только о том, что функ- ции возвращают, так как они не могут сделать чего-либо другого. Это облегчит вам жизнь!.. Даже несмотря на то, что некоторая логика в коде для ввода-вывода необходима (например, открытие файлов и т. п.), она должна быть сведена к минимуму. Чистые функ- ции по умолчанию ленивы; следовательно, мы не знаем, когда они будут вычислены – это не должно иметь значения. Но как только чистые функции начинают вызывать исключения, становится важ- ным момент их выполнения. Вот почему мы можем перехватывать исключения из чистых функций в части кода, посвящённой вво- ду-выводу. И это плохо: ведь мы стремимся оставить такую часть настолько маленькой, насколько возможно!… Однако если мы не перехватываем исключения, наша программа «падает». Решение? Не надо мешать исключения и чистый код! Пользуйтесь преиму- ществами системы типов языка Haskell и используйте типы вроде Either и Maybe для представления результатов, при вычислении ко- торых может произойти ошибка.

 







Последнее изменение этой страницы: 2017-02-17; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.228.10.17 (0.008 с.)