| isIllegalOperation e = putStrLn "Караул! Спасите!"
| otherwise = ioError e
Убедитесь, что вы перевызываете исключение, если оно не подходит под ваши критерии; в противном случае ваша программа иногда будет «падать» молча, что крайне нежелательно.
Модуль System.IO.Error
также экспортирует функции, которые позволяют нам получать атрибуты исключения, например дескриптор файла, вызвавшего исключение, или имя файла. Все эти функции начинаются с префикса ioe
; их полный список вы можете найти в документации. Скажем, мы хотим напечатать имя файла в сообщении об ошибке. Значение fileName
, полученное при помощи функции getArgs
, напечатать нельзя, потому что в обработчик передаётся только значение типа IOException
и он не знает ни о чём другом. Функция зависит только от своих параметров. Но мы можем вызвать функцию ioeGetFileName
, которая по переданному ей исключению возвращает Maybe FilePath
. Функция пытается получить из значения исключения имя файла, если такое возможно. Давайте изменим обработчик так, чтобы он печатал полное имя файла, из-за которого возникло исключение (не забудьте включить функцию ioeGetFileName
в список импорта для модуля System.IO.Error
):
handler :: IOException -> IO ()
handler e
| isDoesNotExistError e =
case ioeGetFileName e of
Just fileName -> putStrLn $ "Файл " ++ fileName ++
" не существует!"
Nothing -> putStrLn "Файл не существует!"
| otherwise = ioError e
where fileName = ioeGetFileName e
В охранном выражении, если предикат isDoesNotExistError
вернёт значение True
, мы использовали выражение case
, чтобы вызвать функцию ioeGetFileName
с параметром e
; затем сделали сопоставление с образцом по возвращённому значению с типом Maybe
. Выражение case
часто используется в случаях, когда вам надо сделать сопоставление с образцом, не создавая новую функцию. Посмотрим, как это сработает:
$ ./linecount dont_exist.txt
Файл dont_exists.txt не существует!
Вы не обязаны использовать один обработчик для перехвата всех исключений в части кода, работающей с системой ввода-вывода. Вы можете перекрыть только отдельные части кода с помощью функции catch
или перекрывать разные участки кода разными обработчиками, например так:
main = do
action1 `catch` handler1
action2 `catch` handler2
launchRockets
Функция action1
использует функцию handler1
в качестве обработчика, а функция action2
использует handler2
. Функция launchRockets
не является параметром функции catch
, так что любое сгенерированное в ней исключение обрушит нашу программу, если только эта функция не использует try
или catch
внутри себя для обработки собственных ошибок. Конечно же, action1
, action2
и launchRockets
– это действия ввода-вывода, которые «склеены» друг с другом блоком do
и, вероятно, определены где-то в другом месте. Это похоже на блоки try–catch
в других языках: вы можете поместить всю вашу программу в один блок try–catch
или защищать отдельные участки программы и перехватывать различные исключения для разных участков.
Вспомогательные функции для работы с исключениями
Ранее в этой главе мы уже познакомились с функциями bracket
и bracketOnError
, которые реализуют наиболее часто используемый сценарий обработки исключений, когда работа с ресурсом состоит из трёх стадий:
• получение ресурса;
• использование ресурса;
• освобождение ресурса.
В наших примерах на первой стадии открывался файл, на второй шла работа с его содержимым, а на третьей файл закрывался. Функция bracket
гарантировала выполнение всех трёх действий, даже если в процессе генерировалось исключение, а функция bracketOnError
запускала третье действие только в случае возникновения исключения.
Обратите внимание, что программист, использующий такого рода функции, не работает непосредственно с исключениями – ему лишь достаточно понимать логику и порядок вызова конкретных действий.
Модуль Control.Exception
содержит ещё несколько подобных функций. Функция finally
обеспечивает гарантированное выполнение некоторого действия по завершении другого действия. Это всего навсего упрощённый вариант функции bracket
. Вот её сигнатура:
finally :: IO a -> IO b -> IO a
В следующем примере текст "Готово!"
печатается в каждом из двух случаев, несмотря на возникновение исключения во втором: