Control.Exception
| Авторские права | (c) Университет Глазго 2001 |
|---|---|
| Лицензия | BSD-стиля (см. файл libraries/base/LICENSE) |
| Поддерживающий | libraries@haskell.org |
| Стабильность | экспериментальная |
| Переносимость | непереносимая (расширенные исключения) |
| Безопасный Haskell | Надежный |
| Язык | Haskell2010 |
Описание
Этот модуль предоставляет поддержку для повышения и обработки встроенных и пользовательских исключений.
Помимо исключений, выбрасываемых IO операциями, исключения могут быть выброшены чистым кодом (неточные исключения) или внешними событиями (асинхронные исключения), но могут быть пойманы только в IO монаде. Для получения более подробной информации см.:
- Семантика неточных исключений, Саймон Пейтон Джонс, Аласдейр Рейд, Тони Хоар, Саймон Марлоу, Фергус Хендерсон, в PLDI'99.
- Асинхронные исключения в Haskell, Саймон Марлоу, Саймон Пейтон Джонс, Энди Моран и Джон Реппи, в PLDI'01.
- Расширяемая динамически типизированная иерархия исключений, Саймон Марлоу, в Haskell '06.
Тип исключения
data SomeException Источник
Тип SomeException является корнем иерархии типов исключений. При выбросе исключения типа e, в скрытом виде оно инкапсулируется в SomeException.
Конструкторы
| forall e.Исключение e => SomeException e |
Примеры
| Show SomeException | С момента: base-3.0 |
Определено в GHC.Exception.Type МетодыshowsPrec :: Int -> SomeException -> ShowS Источник show :: SomeException -> Строка Источник showList :: [SomeException] -> ShowS Источник | |
| Исключение SomeException | С момента: base-3.0 |
Определено в GHC.Exception.Type МетодыtoException :: SomeException -> SomeException Источник fromException :: SomeException -> Возможно SomeException Источник | |
class (Typeable e, Show e) => Исключение e where Источник
Любой тип, который вы хотите бросить или поймать как исключение, должен быть экземпляром класса Exception. Простейший случай — новый тип исключения непосредственно под корнем:
data MyException = ThisException | ThatException
deriving Show
instance Exception MyException
Определения по умолчанию в классе Exception делают то, что нам нужно в этом случае. Теперь вы можете выбрасывать и ловить ThisException и ThatException как исключения:
*Main> throw ThisException `catch` \e -> putStrLn ("Caught " ++ show (e :: MyException))
Caught ThisException
В более сложных примерах вы можете определить целую иерархию исключений:
---------------------------------------------------------------------
-- Make the root exception type for all the exceptions in a compiler
data SomeCompilerException = forall e . Exception e => SomeCompilerException e
instance Show SomeCompilerException where
show (SomeCompilerException e) = show e
instance Exception SomeCompilerException
compilerExceptionToException :: Exception e => e -> SomeException
compilerExceptionToException = toException . SomeCompilerException
compilerExceptionFromException :: Exception e => SomeException -> Maybe e
compilerExceptionFromException x = do
SomeCompilerException a <- fromException x
cast a
---------------------------------------------------------------------
-- Make a subhierarchy for exceptions in the frontend of the compiler
data SomeFrontendException = forall e . Exception e => SomeFrontendException e
instance Show SomeFrontendException where
show (SomeFrontendException e) = show e
instance Exception SomeFrontendException where
toException = compilerExceptionToException
fromException = compilerExceptionFromException
frontendExceptionToException :: Exception e => e -> SomeException
frontendExceptionToException = toException . SomeFrontendException
frontendExceptionFromException :: Exception e => SomeException -> Maybe e
frontendExceptionFromException x = do
SomeFrontendException a <- fromException x
cast a
---------------------------------------------------------------------
-- Make an exception type for a particular frontend compiler exception
data MismatchedParentheses = MismatchedParentheses
deriving Show
instance Exception MismatchedParentheses where
toException = frontendExceptionToException
fromException = frontendExceptionFromException
Теперь мы можем перехватить исключение MismatchedParentheses как MismatchedParentheses, SomeFrontendException или SomeCompilerException, но не других типов, например, IOException.
*Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: MismatchedParentheses))
Caught MismatchedParentheses
*Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: SomeFrontendException))
Caught MismatchedParentheses
*Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: SomeCompilerException))
Caught MismatchedParentheses
*Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: IOException))
*** Exception: MismatchedParentheses
Минимальное полное определение
Ничего
Методы
toException :: e -> SomeException Источник
fromException :: SomeException -> Возможно e Источник
displayException :: e -> Строка Источник
Отобразить значение этого исключения в удобочитаемом виде.
Реализация по умолчанию: show.
С момента: base-4.8.0.0
Примеры
| Исключение НекоторыеИсключения | С момента: base-3.0 |
Определено в GHC.Exception.Type | |
| Исключение АрифметическоеИсключение | С момента: base-4.0.0.0 |
| Исключение ВызовОшибки | С момента: base-4.0.0.0 |
Определено в GHC.Exception | |
| Исключение FileLockingNotSupported | |
| Исключение IOException | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
| Исключение КодВыхода | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
| Исключение FixIOException | С момента: base-4.11.0.0 |
Определено в GHC.IO.Exception | |
| Исключение ArrayException | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
| Исключение AsyncException | С момента: base-4.7.0.0 |
Определено в GHC.IO.Exception | |
| Исключение SomeAsyncException | С момента: base-4.7.0.0 |
Определено в GHC.IO.Exception | |
| Исключение AssertionFailed | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
| Исключение CompactionFailed | С момента: base-4.10.0.0 |
Определено в GHC.IO.Exception | |
| Исключение AllocationLimitExceeded | С момента: base-4.8.0.0 |
| Исключение Тупик | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception МетодыtoException :: Тупик -> НекоторыйИсключение Исходный код fromException :: НекоторыйИсключение -> Возможно Тупик Исходный код | |
| Исключение Заблокирован на неопределенное время в STM | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
| Исключение Заблокирован на неопределенное время на MVar | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
| Исключение Динамический | С момента: base-4.0.0.0 |
Определено в Data.Dynamic | |
| Исключение ВложенноАтомарно | С момента: base-4.0 |
Определено в Control.Exception.Base | |
| Исключение Непрерывность | С момента: base-4.0 |
Определено в Control.Exception.Base | |
| Exception TypeError | Since: base-4.9.0.0 |
Определено в Control.Exception.Base МетодыtoException :: TypeError -> SomeException Исходный код fromException :: SomeException -> Maybe TypeError Исходный код | |
| Exception NoMethodError | Since: base-4.0 |
Определено в Control.Exception.Base | |
| Exception RecUpdError | Since: base-4.0 |
Определено в Control.Exception.Base МетодыtoException :: RecUpdError -> SomeException Исходный код fromException :: SomeException -> Maybe RecUpdError Исходный код | |
| Exception RecConError | Since: base-4.0 |
Определено в Control.Exception.Base МетодыtoException :: RecConError -> SomeException Исходный код fromException :: SomeException -> Maybe RecConError Исходный код | |
| Exception RecSelError | Since: base-4.0 |
Определено в Control.Exception.Base МетодыtoException :: RecSelError -> SomeException Исходный код fromException :: SomeException -> Maybe RecSelError Исходный код | |
| Exception PatternMatchFail | Since: base-4.0 |
| Исключение Таймаут | С момента: base-4.7.0.0 |
Определено в System.Timeout | |
| Исключение Пустота | С момента: base-4.8.0.0 |
Определено в Data.Void | |
data ИсключениеIO Исходный код
Исключения, возникающие в IO монаде. IOException записывает более конкретный тип ошибки, описательную строку и, возможно, дескриптор, используемый при возникновении ошибки.
Экземпляры
| Равно ИсключениеIO | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception Методы(==) :: ИсключениеIO -> ИсключениеIO -> Булево Исходный код (/=) :: ИсключениеIO -> ИсключениеIO -> Булево Исходный код | |
| Вывод ИсключениеIO | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Целое число -> ИсключениеIO -> ВыводS Исходный код show :: ИсключениеIO -> Строка Исходный код showList :: [ИсключениеIO] -> ВыводS Исходный код | |
| Исключение ИсключениеIO | С момента: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
data АрифметическоеИсключение Исходный код
Арифметические исключения.
Конструкторы
| Переполнение | |
| Подпотоки | |
| ПотеряТочности | |
| ДелениеНаНоль | |
| Денормализация | |
| НольЗнаменатель | С момента: base-4.6.0.0 |
Примеры
data ArrayException Источник
Исключения, сгенерированные операциями с массивами
Конструкторы
| IndexOutOfBounds String | Попытка индексирования массива за пределами объявленных границ. |
| UndefinedElement String | Попытка вычисления элемента массива, который не был инициализирован. |
Примеры
newtype ОшибкаУтверждения Исходный код
assert было применено к False.
Конструкторы
| ОшибкаУтверждения Строка |
Примеры реализации
| Show AssertionFailed | С момента выпуска: base-4.1.0.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Int -> AssertionFailed -> ShowS Исходный код show :: AssertionFailed -> String Исходный код showList :: [AssertionFailed] -> ShowS Исходный код | |
| Исключение AssertionFailed | С момента выпуска: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
тип SomeAsyncException Исходный код
Базовый класс для асинхронных исключений.
С момента выпуска: base-4.7.0.0
Конструкторы
| forall e.Исключение e => SomeAsyncException e |
Примеры реализации
| Show SomeAsyncException | С момента выпуска: base-4.7.0.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Int -> SomeAsyncException -> ShowS Исходный код show :: SomeAsyncException -> Строка Исходный код showList :: [SomeAsyncException] -> ShowS Исходный код | |
| Исключение SomeAsyncException | С момента выпуска: base-4.7.0.0 |
Определено в GHC.IO.Exception | |
тип AsyncException Исходный код
Асинхронные исключения.
Конструкторы
| Переполнение стека | Стек текущей нити превысил свой лимит. Поскольку исключение было вызвано, стек нити, безусловно, снова окажется ниже своего лимита, но программист должен немедленно принять меры. |
| Переполнение кучи |
Память кучи программы приближается к своему лимиту, и программа должна принять меры, чтобы уменьшить количество активных данных. Примечания:
|
| Уничтожение нити | Это исключение вызывается другой нитью, вызывающей |
| Прерывание пользователя | Это исключение по умолчанию генерируется в основной нити программы, когда пользователь запрашивает завершение программы с помощью стандартного механизма (например, сочетания клавиш Control-C в консоли). |
Примеры
asyncExceptionToException :: Исключение e => e -> SomeException Исходный код
С момента: base-4.7.0.0
asyncExceptionFromException :: Исключение e => SomeException -> Maybe e Исходный код
С момента: base-4.7.0.0
data Непрерывность Исходный код
Выбрасывается, когда система выполнения обнаруживает, что вычисление гарантированно не завершится. Обратите внимание, что нет гарантии, что система выполнения заметит, гарантированно ли завершится данное вычисление или нет.
Конструкторы
| Непрерывность |
Примеры использования
| Show NonTermination | С версии: base-4.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Int -> NonTermination -> ShowS Исходный код show :: NonTermination -> String Исходный код showList :: [NonTermination] -> ShowS Исходный код | |
| Исключение NonTermination | С версии: base-4.0 |
Определено в Control.Exception.Base | |
data ВложенноАтомарно Исходный код
Выбрасывается, когда программа пытается вызвать atomically, из пакета stm, внутри другого вызова atomically.
Конструкторы
| ВложенноАтомарно |
Примеры использования
| Show ВложенноАтомарно | С версии: base-4.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Int -> ВложенноАтомарно -> ShowS Исходный код show :: ВложенноАтомарно -> String Исходный код showList :: [ВложенноАтомарно] -> ShowS Исходный код | |
| Исключение ВложенноАтомарно | С версии: base-4.0 |
Определено в Control.Exception.Base | |
data ЗаблокированБессрочноНаMVar Исходный код
Поток заблокирован на MVar, но других ссылок на MVar нет, поэтому он не может продолжить работу.
Конструкторы
| ЗаблокированБессрочноНаMVar |
Примеры
| Show BlockedIndefinitelyOnMVar | С тех пор: base-4.1.0.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Int -> BlockedIndefinitelyOnMVar -> ShowS Источник | |
| Исключение BlockedIndefinitelyOnMVar | С тех пор: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
data BlockedIndefinitelyOnSTM Источник
Поток ожидает повторной попытки транзакции STM, но других ссылок на любые TVar нет, поэтому он никогда не сможет продолжить.
Конструкторы
| BlockedIndefinitelyOnSTM |
Примеры
| Show BlockedIndefinitelyOnSTM | С тех пор: base-4.1.0.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Int -> BlockedIndefinitelyOnSTM -> ShowS Источник | |
| Исключение BlockedIndefinitelyOnSTM | С тех пор: base-4.1.0.0 |
Определено в GHC.IO.Exception | |
data AllocationLimitExceeded Источник
Этот поток превысил свой лимит выделения. См. setAllocationCounter и enableAllocationLimit.
С тех пор: base-4.8.0.0
Конструкторы
| AllocationLimitExceeded |
Примеры
| Show AllocationLimitExceeded | С момента: base-4.7.1.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Int -> AllocationLimitExceeded -> ShowS Исходный код | |
| Исключение AllocationLimitExceeded | С момента: base-4.8.0.0 |
Определено в GHC.IO.Exception | |
newtype CompactionFailed Исходный код
Сжатие обнаружило объект, который нельзя сжать. Функции, изменяемые объекты или закрепленные объекты сжать нельзя. См. compact.
С момента: base-4.10.0.0
Конструкторы
| CompactionFailed String |
Примеры
| Show CompactionFailed | С момента: base-4.10.0.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Int -> CompactionFailed -> ShowS Исходный код show :: CompactionFailed -> String Исходный код showList :: [CompactionFailed] -> ShowS Исходный код | |
| Исключение CompactionFailed | С момента: base-4.10.0.0 |
data Deadlock Исходный код
Нет запущенных потоков, поэтому программа заблокирована. Исключение Deadlock генерируется только в главном потоке.
Конструкторы
| Deadlock |
Примеры использования
| Show Deadlock | С момента выпуска: base-4.1.0.0 |
Определено в GHC.IO.Exception МетодыshowsPrec :: Int -> Deadlock -> ShowS Исходный код show :: Deadlock -> Строка Исходный код showList :: [Deadlock] -> ShowS Исходный код | |
| Исключение Deadlock | С момента выпуска: base-4.1.0.0 |
Определено в GHC.IO.Exception МетодыtoException :: Deadlock -> SomeException Исходный код fromException :: SomeException -> Может быть Deadlock Исходный код | |
newtype ОшибкаОтсутствияМетода Исходный код
Был вызван метод класса без определения (ни с определением по умолчанию, ни с определением в соответствующем примере). String содержит информацию о том, какой именно это был метод.
Конструкторы
| ОшибкаОтсутствияМетода Строка |
Примеры использования
| Show ОшибкаОтсутствияМетода | С момента выпуска: base-4.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Int -> ОшибкаОтсутствияМетода -> ShowS Исходный код | |
| Исключение ОшибкаОтсутствияМетода | С момента выпуска: base-4.0 |
newtype ОшибкаНесоответствияШаблону Исходный код
Произошло несоответствие шаблона. String содержит информацию о расположении источника шаблона.
Конструкторы
| ОшибкаНесоответствияШаблону Строка |
Примеры использования
| Show PatternMatchFail | С версии: base-4.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Int -> PatternMatchFail -> ShowS Исходный код show :: PatternMatchFail -> Строка Исходный код showList :: [PatternMatchFail] -> ShowS Исходный код | |
| Исключение PatternMatchFail | С версии: base-4.0 |
Определено в Control.Exception.Base | |
newtype RecConError Исходный код
Использовалось неоинициализированное поле записи. String содержит информацию о расположении источника, где была создана запись.
Конструкторы
| RecConError Строка |
Примеры использования
| Show RecConError | С версии: base-4.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Int -> RecConError -> ShowS Исходный код show :: RecConError -> Строка Исходный код showList :: [RecConError] -> ShowS Исходный код | |
| Исключение RecConError | С версии: base-4.0 |
Определено в Control.Exception.Base | |
newtype RecSelError Исходный код
К селектору записи был применён конструктор без соответствующего поля. Это может произойти только с типом данных, имеющим несколько конструкторов, где некоторые поля есть в одном конструкторе, но отсутствуют в другом. String содержит информацию о расположении селектора записи.
Конструкторы
| RecSelError Строка |
Примеры использования
| Show RecSelError | С версии: base-4.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Int -> RecSelError -> ShowS Исходный код show :: RecSelError -> Строка Исходный код showList :: [RecSelError] -> ShowS Исходный код | |
| Исключение RecSelError | С версии: base-4.0 |
Определено в Control.Exception.Base | |
newtype RecUpdError Исходный код
Обновление записи выполнено для конструктора без соответствующего поля. Это может произойти только с типом данных, имеющим несколько конструкторов, где некоторые поля присутствуют в одном конструкторе, но отсутствуют в другом. String содержит информацию о месте происхождения обновления записи.
Конструкторы
| RecUpdError Строка |
Примеры использования
| Show RecUpdError | С версии: base-4.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Целое -> RecUpdError -> ShowS Исходный код show :: RecUpdError -> Строка Исходный код showList :: [RecUpdError] -> ShowS Исходный код | |
| Исключение RecUpdError | С версии: base-4.0 |
Определено в Control.Exception.Base | |
data ErrorCall Исходный код
Это выбрасывается, когда пользователь вызывает error. Первое String — это аргумент, переданный error, а второе String — местоположение.
Конструкторы
| ErrorCallWithLocation Строка Строка |
Примеры использования
newtype ОшибкаТипизации Исходный код
Вызвана ошибка типизации во время компиляции. Это возможно только с помощью флага -fdefer-type-errors. String предоставляет подробности о несоответствии типов.
С момента выпуска: base-4.9.0.0
Конструкторы
| ОшибкаТипизации Строка |
Примеры использования
| Show TypeError | С момента выпуска: base-4.9.0.0 |
Определено в Control.Exception.Base МетодыshowsPrec :: Int -> TypeError -> ShowS Исходный код show :: TypeError -> Строка Исходный код showList :: [TypeError] -> ShowS Исходный код | |
| Исключение TypeError | С момента выпуска: base-4.9.0.0 |
Определено в Control.Exception.Base МетодыtoException :: TypeError -> SomeException Исходный код fromException :: SomeException -> Возможно TypeError Исходный код | |
Выбрасывание исключений
throw :: forall (r :: Тип исполнения). forall (a :: TYPE r). forall e. Исключение e => e -> a Исходный код
Выбросить исключение. Исключения могут быть выброшены из чисто функционального кода, но могут быть перехвачены только в IO монаде.
throwIO :: Исключение e => e -> IO a Исходный код
Вариант throw, который может быть использован только в IO монаде.
Хотя throwIO имеет тип, являющийся экземпляром типа throw, эти две функции немного отличаются:
throw e `seq` x ===> throw e throwIO e `seq` x ===> x
Первый пример вызовет исключение e, а второй — нет. Фактически, throwIO вызовет исключение только при использовании в IO монаде. Вариант throwIO следует использовать вместо throw для выброса исключения в IO монаде, так как он гарантирует порядок с другими IO операциями, а throw — нет.
ioError :: ОшибкаIO -> IO a Исходный код
Выбросить IOError в IO монаде.
throwTo :: Исключение e => Идентификатор потока -> e -> IO () Исходный код
throwTo выбросит произвольное исключение в целевом потоке (только GHC).
Доставка исключений синхронизируется между исходным и целевым потоком: throwTo не вернется, пока исключение не будет выброшено в целевом потоке. Таким образом, вызывающий поток может быть уверен, что целевой поток получил исключение. Доставка исключений также атомарна по отношению к другим исключениям. Атомарность полезна при работе с гонками: например, если два потока могут убивать друг друга, гарантируется, что только один из потоков сможет убить другого.
Любая работа, которую выполнял целевой поток при выбросе исключения, не теряется: вычисление приостанавливается до тех пор, пока не потребуется другим потоком.
Если целевой поток в данный момент выполняет внешний вызов, то исключение не будет выброшено (и, следовательно, throwTo не вернётся) до завершения вызова. Это верно независимо от того, находится ли вызов внутри mask или нет. Однако в GHC внешний вызов может быть помечен как interruptible, в этом случае throwTo заставит RTS попытаться заставить вызов вернуть значение; см. документацию GHC для получения дополнительной информации.
Важно отметить: поведение throwTo отличается от описанного в статье "Асинхронные исключения в Haskell" (http://research.microsoft.com/~simonpj/Papers/asynch-exns.htm). В статье throwTo неблокирующая, но библиотечная реализация использует более синхронный дизайн, в котором throwTo не возвращается, пока исключение не будет получено целевым потоком. Компромисс обсуждается в разделе 9 статьи. Как любая блокирующая операция, throwTo поэтому прерывимая (см. раздел 5.3 статьи). Однако в отличие от других прерывимых операций, throwTo всегда прерывимая, даже если она фактически не блокируется.
Нет гарантии, что исключение будет доставлено незамедлительно, хотя среда выполнения будет стремиться к тому, чтобы не возникало произвольных задержек. В GHC исключение может быть выброшено только тогда, когда поток достигает безопасной точки, где безопасная точка — это место, где происходит выделение памяти. Некоторые циклы не выполняют никакого выделения памяти внутри цикла и поэтому не могут быть прерваны throwTo.
Если целевой объект throwTo является вызывающим потоком, то поведение идентично throwIO, за исключением того, что исключение выброшено как асинхронное исключение. Это означает, что если есть окружающее чистое вычисление, что будет иметь место, если текущая операция IO находится внутри unsafePerformIO или unsafeInterleaveIO, это вычисление не заменяется исключением постоянно, а приостанавливается так, как если бы оно получило асинхронное исключение.
Обратите внимание, что если throwTo вызывается с текущим потоком в качестве целевого, исключение будет выброшено, даже если поток в данный момент находится внутри mask или uninterruptibleMask.
Перехват исключений
Существует несколько функций для перехвата и проверки исключений; все они могут быть использованы только изнутри IO монады.
Вот практическое правило для определения, какую функцию для перехвата использовать:
- Если нужно выполнить некоторые действия по очистке в случае выброса исключения, используйте
finally,bracketилиonException. - Чтобы восстановиться после исключения и сделать что-то другое, лучше всего использовать одну из функций
try. - ...если вы восстанавливаетесь после асинхронного исключения, используйте
catchилиcatchJust.
Разница между использованием try и catch для восстановления заключается в том, что в catch обработчик находится внутри неявной mask (см. "Асинхронные исключения"), что важно при перехвате асинхронных исключений, но при перехвате других типов исключений это не нужно. Более того, возможно случайно остаться внутри неявной mask путем хвостового вызова, а не возвращения из обработчика, поэтому мы рекомендуем использовать try вместо catch для обычного восстановления от исключения.
Типичное использование tryJust для восстановления выглядит так:
do r <- tryJust (guard . isDoesNotExistError) $ getEnv "HOME"
case r of
Left e -> ...
Right home -> ...
Перехват всех исключений
Можно перехватить все исключения, используя тип SomeException:
catch f (\e -> ... (e :: SomeException) ...)
ОДНАКО, это обычно не то, что вы хотите сделать!
Например, предположим, что вы хотите прочитать файл, но если он не существует, то продолжить, как если бы он содержал "". Вы, возможно, захотите просто перехватить все исключения и вернуть "" в обработчике. Однако это имеет множество нежелательных последствий. Например, если пользователь нажмёт комбинацию клавиш Control-C в нужный момент, то исключение UserInterrupt будет перехвачено, и программа продолжит работу, полагая, что файл содержит "". Аналогично, если другая нить попытается убить нить, читающую файл, то исключение ThreadKilled будет проигнорировано.
Вместо этого вы должны перехватывать только те исключения, которые вам действительно нужны. В этом случае это, вероятно, будет более конкретно, чем даже "любое исключение IO"; ошибка доступа, скорее всего, также должна обрабатываться по-другому. Вместо этого вы, вероятно, захотите что-то вроде:
e <- tryJust (guard . isDoesNotExistError) (readFile f) let str = either (const "") id e
Бывают случаи, когда вам действительно нужно перехватить любой вид исключения. Однако в большинстве случаев это необходимо только для того, чтобы выполнить некоторые очистительные операции; вас фактически не интересует само исключение. Например, если вы открываете файл, то хотите закрыть его снова, независимо от того, выполняется обработка файла нормально или возникает исключение. Однако в этих случаях вы можете использовать функции, такие как bracket, finally и onException, которые никогда фактически не передают вам исключение, а просто вызывают функции очистки в соответствующие моменты.
Но иногда вам действительно нужно перехватить любое исключение и фактически увидеть, что это за исключение. Один пример — в самом верхнем уровне программы, вы можете захотеть перехватить любое исключение, вывести его в лог-файл или на экран, а затем завершиться корректно. Для этих случаев вы можете использовать catch (или одну из других функций перехвата исключений) с типом SomeException.
Функции catch
Аргументы
| :: Исключение e | |
| => IO a | Вычисляемый фрагмент кода |
| -> (e -> IO a) | Обработчик для вызова, если возникает исключение |
| -> IO a |
Это самая простая из функций перехвата исключений. Она принимает один аргумент, выполняет его, и если возникает исключение, выполняется «обработчик», с значением исключения, переданным в качестве аргумента. В противном случае результат возвращается как обычно. Например:
catch (readFile f)
(\e -> do let err = show (e :: IOException)
hPutStr stderr ("Warning: Couldn't open " ++ f ++ ": " ++ err)
return "")
Обратите внимание, что мы должны указать тип для e, иначе программа не пройдёт типизацию, так как тип является неоднозначным. Хотя можно перехватывать исключения любого типа, см. раздел «Перехват всех исключений» (в Control.Exception) для объяснения проблем, связанных с этим.
Для перехвата исключений в чистых (не-IO) выражениях, см. функцию evaluate.
Обратите внимание, что из-за неопределённого порядка вычислений в Haskell выражение может выбросить одно из нескольких возможных исключений: рассмотрите выражение (error "urk") + (1 `div` 0). Выбрасывает ли выражение ErrorCall "urk", или DivideByZero?
Ответ — «может выбросить любое из них»; выбор не детерминирован. Если вы перехватываете любой тип исключения, то вы можете перехватить любое из них. Если вы вызываете catch с типом IO Int -> (ArithException -> IO Int) -> IO Int, то обработчик может быть вызван со значением DivideByZero в качестве аргумента или исключение ErrorCall "urk" может быть распространено дальше вверх. Если вы вызовете его снова, вы можете получить обратное поведение. Это нормально, так как catch — это IO вычисление.
catches :: IO a -> [Обработчик a] -> IO a Источник
Иногда вам нужно перехватить два разных типа исключений. Вы могли бы сделать что-то вроде
f = expr `catch` \ (ex :: ArithException) -> handleArith ex
`catch` \ (ex :: IOException) -> handleIO ex
Однако есть несколько проблем с этим подходом. Во-первых, наличие двух обработчиков исключений неэффективно. Однако более серьёзная проблема заключается в том, что второй обработчик исключений перехватит исключения в первом, например, в приведённом выше примере, если handleArith сгенерирует исключение IOException, то второй обработчик исключений перехватит его.
Вместо этого мы предоставляем функцию catches, которая используется следующим образом:
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
Handler (\ (ex :: IOException) -> handleIO ex)]
data Обработчик a Источник
Это необходимо при использовании catches.
Конструкторы
| forall e.Исключение e => Обработчик (e -> IO a) |
Примеры
| Функтор Обработчик | С версии: base-4.6.0.0 |
Определено в Control.Exception Методыfmap :: (a -> b) -> Обработчик a -> Обработчик b Источник (<$) :: a -> Обработчик b -> Обработчик a Источник | |
Аргументы
| :: Исключение e | |
| => (e -> Может быть b) | Предикат для выбора исключений |
| -> IO a | Вычисляемый фрагмент кода |
| -> (b -> IO a) | Обработчик |
| -> IO a |
Функция catchJust похожа на catch, но она принимает дополнительный аргумент, который является предикатом исключения, функцией, которая выбирает типы исключений, которые нас интересуют.
catchJust (\e -> if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing)
(readFile f)
(\_ -> do hPutStrLn stderr ("No such file: " ++ show f)
return "")
Любые другие исключения, которые не соответствуют предикату, повторно генерируются и могут быть перехвачены вложенным catch, catchJust и т. д.
Функции handle
handle :: Исключение e => (e -> IO a) -> IO a -> IO a Источник
Вариант catch с переставленными аргументами; полезно в ситуациях, когда код обработчика короче. Например:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $
...
handleJust :: Исключение e => (e -> Может быть b) -> (b -> IO a) -> IO a -> IO a Источник
Вариант catchJust с переставленными аргументами (см. handle).
Функции try
try :: Исключение e => IO a -> IO (Either e a) Источник
Аналогично catch, но возвращает результат типа Either, который равен (Right a), если не было сгенерировано исключение типа e, или (Left ex), если было сгенерировано исключение типа e и его значение равно ex. Если возникает исключение другого типа, оно будет распространено к следующему вложенному обработчику исключений.
try a = catch (Right `liftM` a) (return . Left)
tryJust :: Exception e => (e -> Maybe b) -> IO a -> IO (Either b a) Source
Вариант функции try, который принимает предикат исключения для выбора, какие исключения будут перехвачены (см. catchJust). Если исключение не соответствует предикату, оно повторно выбрасывается.
Функция evaluate
Вычисляет аргумент до слабой нормальной формы головного уровня.
evaluate обычно используется для обнаружения любых исключений, которые может содержать ленивое значение, и, возможно, для их обработки.
evaluate вычисляет только до слабой нормальной формы головного уровня. Если требуется более глубокое вычисление, функция force из Control.DeepSeq может быть полезной:
evaluate $ force x
Существует тонкое различие между evaluate x и return $! x, аналогичное различию между throwIO и throw. Если ленивое значение x вызывает исключение, return $! x не сможет вернуть действие IO и вместо этого выбросит исключение. evaluate x, с другой стороны, всегда создаёт действие IO; это действие выбросит исключение при выполнении тогда и только тогда, когда x выбросит исключение при вычислении.
Практическим следствием этого различия является то, что из-за семантики *неточных исключений*:
(return $! error "foo") >> error "bar"
может выбросить либо "foo", либо "bar", в зависимости от оптимизаций, выполненных компилятором. С другой стороны,
evaluate (error "foo") >> error "bar"
гарантированно выбросит "foo".
Правило заключается в том, чтобы использовать evaluate для принудительного вывода или обработки исключений в ленивых значениях. Если же вы принудительно выводите ленивое значение только для повышения эффективности и не беспокоитесь об исключениях, вы можете использовать return $! x.
Функция mapException
mapException :: (Exception e1, Exception e2) => (e1 -> e2) -> a -> a Source
Эта функция отображает одно исключение в другое, как предложено в статье "Семантика для неточных исключений".
Асинхронные исключения
Асинхронные исключения возникают из-за внешних факторов и могут быть вызваны в любой момент выполнения. StackOverflow и HeapOverflow — два примера системных асинхронных исключений.
Однако основным источником асинхронных исключений является throwTo:
throwTo :: ThreadId -> Exception -> IO ()
throwTo (также killThread) позволяет одному работающему потоку вызывать произвольное исключение в другом потоке. Таким образом, исключение является асинхронным по отношению к целевому потоку, который может заниматься чем угодно в момент получения исключения. С асинхронными исключениями следует обращаться с большой осторожностью; очень легко ввести гонки из-за чрезмерного использования throwTo.
Управление асинхронными исключениями
Следующие функции позволяют потоку управлять доставкой асинхронных исключений во время критической области.
mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Source
Выполняет вычисление IO с *замаскированными* асинхронными исключениями. То есть любой поток, пытающийся вызвать исключение в текущем потоке с помощью throwTo будет заблокирован до тех пор, пока асинхронные исключения не будут снова размаскированы.
Аргумент, передаваемый в mask, — это функция, которая принимает в качестве аргумента другую функцию, которая может быть использована для восстановления текущего состояния маскировки в контексте вычисления с маской. Например, распространённый способ использования mask — защита приобретения ресурса:
mask $ \restore -> do
x <- acquire
restore (do_something_with x) `onException` release
release
Этот код гарантирует, что acquire связано с release, замаскировав асинхронные исключения для критических участков. (Вместо того, чтобы писать этот код самостоятельно, лучше использовать bracket, который абстрагирует общий шаблон).
Обратите внимание, что действие restore, переданное в качестве аргумента mask, не обязательно размаскирует асинхронные исключения, оно только восстанавливает состояние маскировки до состояния окружающего контекста. Таким образом, если асинхронные исключения уже замаскированы, mask не может использоваться для размаскирования исключений снова. Это сделано для того, чтобы, если вы вызываете функцию библиотеки с замаскированными исключениями, вы могли быть уверены, что функция библиотеки не сможет размаскировать исключения. Если вы пишете код библиотеки и вам нужно использовать асинхронные исключения, единственный способ — создать новый поток; см. forkIOWithUnmask.
Асинхронные исключения всё ещё могут быть получены во время замаскированного состояния, если замаскированный поток *блокируется* определёнными способами; см. Control.Exception.
Потоки, созданные forkIO, наследуют состояние MaskingState от родителя; то есть, чтобы запустить поток в состоянии MaskedInterruptible, используйте mask_ $ forkIO .... Это особенно полезно, если вам нужно установить обработчик исключений в разветвлённом потоке до получения каких-либо асинхронных исключений. Чтобы создать новый поток в размаскированном состоянии, используйте forkIOWithUnmask.
Аналогично mask, но не передаёт действие restore в аргумент.
uninterruptibleMask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Source
Аналогично mask, но вычисление с маской не прерывается (см. Control.Exception). К ЭТОМУ СЛЕДУЕТ ОТНОСИТЬСЯ С БОЛЬШОЙ ОСТОРОЖНОСТЬЮ, потому что если поток, выполняющий вычисление с маской, заблокируется по какой-либо причине, то поток (и, возможно, программа, если это основной поток) станет нереагирующим и неубиваемым. Эта функция должна потребоваться только в том случае, если вам нужно замаскировать исключения вокруг прерываемого действия, и вы можете гарантировать, что прерываемое действие будет блокироваться только на короткий период времени.
uninterruptibleMask_ :: IO a -> IO a Source
Аналогично uninterruptibleMask, но не передаёт действие restore в аргумент.
data MaskingState Source
Описывает поведение потока при получении асинхронного исключения.
Конструкторы
| Unmasked | асинхронные исключения не замаскированы (нормальное состояние) |
| MaskedInterruptible | состояние во время |
| MaskedUninterruptible | состояние во время |
Примеры
| Eq MaskingState | С версии: base-4.3.0.0 |
Определено в GHC.IO Методы(==) :: MaskingState -> MaskingState -> Bool Исходный код (/=) :: MaskingState -> MaskingState -> Bool Исходный код | |
| Show MaskingState | С версии: base-4.3.0.0 |
Определено в GHC.IO МетодыshowsPrec :: Int -> MaskingState -> ShowS Исходный код show :: MaskingState -> String Исходный код showList :: [MaskingState] -> ShowS Исходный код | |
getMaskingState :: IO MaskingState Исходный код
Возвращает MaskingState для текущей нити.
interruptible :: IO a -> IO a Исходный код
Разрешает поднятие асинхронных исключений даже внутри mask, делая операцию прерываемой (см. обсуждение «Прерываемых операций» в Exception).
При вызове вне mask, или внутри uninterruptibleMask, эта функция не имеет эффекта.
С версии: base-4.9.0.0
allowInterrupt :: IO () Исходный код
При вызове внутри mask, эта функция разрешает поднятие замаскированного асинхронного исключения, если оно существует. Эквивалентно выполнению прерываемой операции (см. #interruptible), но не включает в себя реальное ожидание.
При вызове вне mask, или внутри uninterruptibleMask, эта функция не имеет эффекта.
С версии: base-4.4.0.0
Применение mask к обработчику исключений
Есть неявное mask вокруг каждого обработчика исключений в вызове одной из функций семейства catch. Это то, что нужно в большинстве случаев — это устраняет распространенную гонку при запуске обработчика исключений, потому что может не быть обработчика исключений в стеке для обработки другого исключения, если оно появится сразу. Однако, если асинхронные исключения замаскированы при входе в обработчик, у нас есть время для установки нового обработчика исключений перед прерыванием. Если бы это не было по умолчанию, пришлось бы написать что-то вроде
mask $ \restore ->
catch (restore (...))
(\e -> handler)
Если вам нужно снова размаскировать асинхронные исключения в обработчике исключений, restore можно использовать там тоже.
Обратите внимание, что try и аналогичные функции не имеют такого же значения по умолчанию, потому что в этом случае нет обработчика исключений. Не используйте try для восстановления после асинхронного исключения.
Прерываемые операции
Некоторые операции являются прерываемыми, что означает, что они могут получать асинхронные исключения даже в рамках mask. Любая функция, которая может сама блокироваться, определяется как прерываемая; это включает takeMVar (но не tryTakeMVar), и большинство операций, которые выполняют некоторое ввод/вывод с внешним миром. Причина наличия прерываемых операций заключается в том, что мы можем написать такие вещи, как
mask $ \restore -> do
a <- takeMVar m
catch (restore (...))
(\e -> ...)
Если бы takeMVar не была прерываемой, то это конкретное сочетание могло привести к тупику, потому что нить сама была бы заблокирована в состоянии, в котором она не может получить какие-либо асинхронные исключения. Однако, с прерываемой takeMVar, мы можем быть уверены, что нить может получать исключения вплоть до момента, когда takeMVar завершается успешно. Аналогичные аргументы применяются для других прерываемых операций, таких как openFile.
Полезно рассматривать mask не как способ полностью предотвратить асинхронные исключения, а как способ переключения с асинхронного режима на режим опроса. Основная сложность с асинхронными исключениями заключается в том, что они обычно могут возникать где угодно, но в рамках mask асинхронное исключение поднимается только операциями, которые прерываемы (или вызывают другие прерываемые операции). Во многих случаях эти операции сами могут поднимать исключения, такие как ошибки ввода/вывода, поэтому вызывающая сторона обычно готова обработать исключения, возникающие из операции. Для выполнения явного опроса асинхронных исключений внутри mask, используйте allowInterrupt.
Иногда слишком сложно обрабатывать исключения в середине критической части состоявшего кода. Есть три способа справиться с этой ситуацией:
- Используйте STM. Поскольку транзакция всегда полностью выполняется или вообще не выполняется, транзакции — хороший способ поддерживать инварианты состояния в присутствии асинхронных (и, действительно, синхронных) исключений.
- Используйте
mask, и избегайте прерываемых операций. Для этого нам нужно знать, какие операции прерываемы. Невозможно знать для любой функции библиотеки, может ли она вызвать прерываемую операцию внутри; поэтому вместо этого мы даем список операций, гарантированно не прерываемых ниже. - Используйте
uninterruptibleMask. Это вообще не рекомендуется, если вы не можете гарантировать, что любые прерываемые операции, вызванные в рамкахuninterruptibleMask, могут блокироваться только в течение короткого времени. В противном случае,uninterruptibleMask— хороший способ заставить вашу программу зависнуть и не реагировать на прерывания пользователя.
Следующие операции гарантированно не прерываемы:
- операции над
IORefиз Data.IORef - STM-транзакции, которые не используют
retry - всё из модулей
Foreign - всё из Control.Exception за исключением
throwTo -
tryTakeMVar,tryPutMVar,isEmptyMVar -
takeMVarеслиMVarопределенно заполнен, и, наоборот,putMVarеслиMVarопределенно пуст -
newEmptyMVar,newMVar -
forkIO,myThreadId
Утверждения
assert :: Bool -> a -> a Исходный код
Если первое аргумент вычисляется до True, то результатом является второй аргумент. В противном случае поднимается исключение AssertionFailed, содержащее String с именем исходного файла и номером строки вызова assert.
Утверждения обычно можно включать или выключать с помощью флага компилятора (для GHC, утверждения обычно включены, если оптимизация не включена с помощью -O или флаг -fignore-asserts задан). Когда утверждения отключены, первый аргумент для assert игнорируется, а в качестве результата возвращается второй аргумент.
Утилиты
Когда вы хотите получить ресурс, выполнить с ним некоторую работу и затем освободить его, рекомендуется использовать bracket, так как bracket установит необходимый обработчик исключений для освобождения ресурса в случае возникновения исключения во время вычисления. Если возникнет исключение, то bracket повторно возбудит исключение (после выполнения освобождения).
Примеры часто встречаются при открытии файла:
bracket
(openFile "filename" ReadMode)
(hClose)
(\fileHandle -> do { ... })
Аргументы для bracket расположены в таком порядке, чтобы мы могли частично применить его, например:
withFile name mode = bracket (openFile name mode) hClose
bracket_ :: IO a -> IO b -> IO c -> IO c Source
Вариант bracket, где возвращаемое значение от первого вычисления не требуется.
Аргументы
| :: IO a | вычисление, которое выполняется первым ("получить ресурс") |
| -> (a -> IO b) | вычисление, которое выполняется последним ("освободить ресурс") |
| -> (a -> IO c) | вычисление, которое выполняется между ними |
| -> IO c |
Подобно bracket, но выполняет только конечное действие, если в промежуточном вычислении возникло исключение.
Аргументы
| :: IO a | вычисление, которое выполняется первым |
| -> IO b | вычисление, которое выполняется после (даже если возникло исключение) |
| -> IO a |
Специализированный вариант bracket с только вычислением для выполнения после.
onException :: IO a -> IO b -> IO a Source
Подобно finally, но выполняет только конечное действие, если в вычислении возникло исключение.
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/8.10.2/docs/html/libraries/base-4.14.1.0/Control-Exception.html