Control.Exception
| Авторское право | (c) Университет Глазго, 2001 |
|---|---|
| Лицензия | в стиле BSD (см. файл libraries/base/LICENSE) |
| Сопровождающий | libraries@haskell.org |
| Стабильность | стабильный |
| Переносимость | непереносимый (расширенные исключения) |
| Безопасный Haskell | Доверенный |
| Язык | Haskell2010 |
Описание
Этот модуль предоставляет средства для генерации и перехвата как встроенных, так и пользовательских исключений.
Помимо исключений, генерируемых операциями IO, исключения могут генерироваться чистым кодом (неточные исключения) или внешними событиями (асинхронные исключения), но перехватывать их можно только в монаде IO. Дополнительные сведения см. в следующих работах:
- Семантика неточных исключений, авторы: Саймон Пейтон Джонс, Аластер Рид, Тони Хоар, Саймон Марлоу, Фергус Хендерсон, в сборнике PLDI'99.
- Асинхронные исключения в Haskell, авторы: Саймон Марлоу, Саймон Пейтон Джонс, Энди Моран и Джон Реппи, в сборнике PLDI'01.
- Расширяемая иерархия исключений с динамической типизацией, автор: Саймон Марлоу, в сборнике Haskell '06.
Тип SomeException
data SomeException Источник
Тип SomeException является корнем иерархии типов исключений. Когда генерируется исключение типа e, оно неявно оборачивается в SomeException.
Конструкторы
| (Exception e, HasExceptionContext) => SomeException e |
Экземпляры
| Exception SomeException Источник |
Этот экземпляр отбрасывает все присоединённые С версии: base-3.0 |
Определён в GHC.Internal.Exception.Type МетодыtoException :: SomeException -> SomeException Источник fromException :: SomeException -> Maybe SomeException Источник | |
| Show SomeException Источник | С версии: ghc-internal-3.0 |
Определён в GHC.Internal.Exception.Type МетодыshowsPrec :: Int -> SomeException -> ShowS Источник show :: SomeException -> String Источник showList :: [SomeException] -> ShowS Источник | |
Класс Exception
class (Typeable e, Show e) => Exception 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
Минимальное полное определение
Nothing
Методы
toException :: e -> SomeException Источник
toException должен возвращать значение SomeException без присоединённого ExceptionContext.
fromException :: SomeException -> Maybe e Источник
displayException :: e -> String Источник
Представляет это значение исключения в удобочитаемом виде.
Реализация по умолчанию: show.
С версии: base-4.8.0.0
backtraceDesired :: e -> Bool Источник
С версии: base-4.20.0.0
Экземпляры
addExceptionContext :: ExceptionAnnotation a => a -> SomeException -> SomeException Источник
Добавляет дополнительные ExceptionContext к SomeException.
someExceptionContext :: SomeException -> ExceptionContext Источник
Просмотреть ExceptionContext у SomeException.
annotateIO :: ExceptionAnnotation e => e -> IO a -> IO a Источник
Выполняет действие IO, добавляя заданный ExceptionContext ко всем выброшенным синхронным исключениям.
С версии: base-4.20.0.0
newtype NoBacktrace e Источник
Конструкторы
| NoBacktrace e |
Экземпляры
| Exception e => Exception (NoBacktrace e) Источник | |
Определено в GHC.Internal.Exception.Type МетодыtoException :: NoBacktrace e -> SomeException Источник fromException :: SomeException -> Maybe (NoBacktrace e) Источник displayException :: NoBacktrace e -> String Источник backtraceDesired :: NoBacktrace e -> Bool Источник | |
| Show e => Show (NoBacktrace e) Источник | |
Определено в GHC.Internal.Exception.Type МетодыshowsPrec :: Int -> NoBacktrace e -> ShowS Источник show :: NoBacktrace e -> String Источник showList :: [NoBacktrace e] -> ShowS Источник | |
data ExceptionWithContext a Источник
Оборачивает конкретное исключение, предоставляя доступ к его ExceptionContext. Предназначено для использования при catch исключений в случаях, когда требуется доступ к контексту.
Конструкторы
| ExceptionWithContext ExceptionContext a |
Экземпляры
| Exception a => Exception (ExceptionWithContext a) Источник | |
Определено в GHC.Internal.Exception.Type МетодыtoException :: ExceptionWithContext a -> SomeException Источник fromException :: SomeException -> Maybe (ExceptionWithContext a) Источник displayException :: ExceptionWithContext a -> String Источник | |
| Show a => Show (ExceptionWithContext a) Источник | |
Определено в GHC.Internal.Exception.Type МетодыshowsPrec :: Int -> ExceptionWithContext a -> ShowS Источник show :: ExceptionWithContext a -> String Источник showList :: [ExceptionWithContext a] -> ShowS Источник | |
data WhileHandling Источник
WhileHandling используется для аннотирования повторно выбрасываемых исключений. Изучив аннотацию WhileHandling, можно восстановить все места, где исключение было выброшено повторно.
Конструкторы
| WhileHandling SomeException |
Экземпляры
| ExceptionAnnotation WhileHandling Исходный код | |
Определён в GHC.Internal.Exception.Type МетодыdisplayExceptionAnnotation :: WhileHandling -> String Исходный код | |
| Show WhileHandling Исходный код | |
Определён в GHC.Internal.Exception.Type МетодыshowsPrec :: Int -> WhileHandling -> ShowS Исходный код show :: WhileHandling -> String Исходный код showList :: [WhileHandling] -> ShowS Исходный код | |
Конкретные типы исключений
data IOException Исходный код
Исключения, возникающие в монаде IO. IOException содержит более конкретный тип ошибки, описательную строку и, возможно, дескриптор, использовавшийся при возникновении ошибки.
Экземпляры
| Eq IOException Исходный код | С версии: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception Методы(==) :: IOException -> IOException -> Bool Исходный код (/=) :: IOException -> IOException -> Bool Исходный код | |
| Exception IOException Исходный код | С версии: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception | |
| Show IOException Исходный код | С версии: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> IOException -> ShowS Исходный код show :: IOException -> String Исходный код showList :: [IOException] -> ShowS Исходный код | |
data ArithException Исходный код
Арифметические исключения.
Конструкторы
| Overflow | |
| Underflow | |
| LossOfPrecision | |
| DivideByZero | |
| Denormal | |
| RatioZeroDenominator | С версии: base-4.6.0.0 |
Экземпляры
data ArrayException Исходный код
Исключения, возникающие при операциях с массивами
Конструкторы
| IndexOutOfBounds String | Предпринята попытка обратиться к элементу массива за пределами объявленных границ. |
| UndefinedElement String | Предпринята попытка вычислить элемент массива, который не был инициализирован. |
Экземпляры
newtype AssertionFailed Источник
Конструкторы
| AssertionFailed String |
Экземпляры
| Exception AssertionFailed Источник | Начиная с: base-4.1.0.0 |
Определено в GHC.Internal.IO.Exception | |
| Show AssertionFailed Источник | Начиная с: base-4.1.0.0 |
Определено в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> AssertionFailed -> ShowS Источник show :: AssertionFailed -> String Источник showList :: [AssertionFailed] -> ShowS Источник | |
newtype NoMethodError Источник
Был вызван метод класса без определения (ни определения по умолчанию, ни определения в соответствующем экземпляре). String содержит сведения о том, какой именно метод это был.
Конструкторы
| NoMethodError String |
Экземпляры
| Exception NoMethodError Источник | Начиная с: base-4.0 |
Определено в GHC.Internal.Control.Exception.Base МетодыtoException :: NoMethodError -> SomeException Источник fromException :: SomeException -> Maybe NoMethodError Источник | |
| Show NoMethodError Источник | Начиная с: base-4.0 |
Определено в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> NoMethodError -> ShowS Источник show :: NoMethodError -> String Источник showList :: [NoMethodError] -> ShowS Источник | |
newtype PatternMatchFail Источник
Сопоставление с образцом завершилось неудачно. String содержит сведения о местоположении образца в исходном коде.
Конструкторы
| PatternMatchFail String |
Экземпляры
| Exception PatternMatchFail Источник | Начиная с: base-4.0 |
Определено в GHC.Internal.Control.Exception.Base | |
| Show PatternMatchFail Источник | Начиная с: base-4.0 |
Определено в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> PatternMatchFail -> ShowS Источник show :: PatternMatchFail -> String Источник showList :: [PatternMatchFail] -> ShowS Источник | |
newtype RecConError Источник
Было использовано неинициализированное поле записи. String содержит сведения о местоположении в исходном коде, где была создана запись.
Конструкторы
| RecConError String |
Экземпляры
| Exception RecConError Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base | |
| Show RecConError Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> RecConError -> ShowS Исходный код show :: RecConError -> String Исходный код showList :: [RecConError] -> ShowS Исходный код | |
newtype RecSelError Исходный код
Селектор записи был применён к конструктору, в котором нет соответствующего поля. Это возможно только для типа данных с несколькими конструкторами, когда одни поля есть в одном конструкторе, но отсутствуют в другом. String содержит сведения об исходном расположении селектора записи.
Конструкторы
| RecSelError String |
Экземпляры
| Exception RecSelError Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base | |
| Show RecSelError Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> RecSelError -> ShowS Исходный код show :: RecSelError -> String Исходный код showList :: [RecSelError] -> ShowS Исходный код | |
newtype RecUpdError Исходный код
Обновление записи было выполнено для конструктора, в котором нет соответствующего поля. Это возможно только для типа данных с несколькими конструкторами, когда одни поля есть в одном конструкторе, но отсутствуют в другом. String содержит сведения об исходном расположении обновления записи.
Конструкторы
| RecUpdError String |
Экземпляры
| Exception RecUpdError Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base | |
| Show RecUpdError Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> RecUpdError -> ShowS Исходный код show :: RecUpdError -> String Исходный код showList :: [RecUpdError] -> ShowS Исходный код | |
data ErrorCall Исходный код
Это исключение возникает, когда пользователь вызывает error. String — это аргумент, переданный в error.
Исторически для местоположения имелось второе String, но с появлением механизмов трассировки стека оно стало избыточным (начиная с base-4.22).
Объединённые шаблоны
| pattern ErrorCallWithLocation :: String -> String -> ErrorCall | Устарело: ErrorCallWithLocation объявлен устаревшим в пользу ErrorCall (который не содержит местоположения). Теперь трассировки стека обрабатываются исключительно механизмами исключений для трассировки стека. |
Экземпляры
newtype TypeError Исходный код
Было вызвано выражение, не прошедшее проверку типов во время компиляции. Это возможно только с параметром -fdefer-type-errors. String содержит сведения о неудачной проверке типов.
Начиная с: base-4.9.0.0
Экземпляры
| Exception TypeError Исходный код | С версии: base-4.9.0.0 |
Определено в GHC.Internal.Control.Exception.Base МетодыtoException :: TypeError -> SomeException Исходный код fromException :: SomeException -> Maybe TypeError Исходный код | |
| Show TypeError Исходный код | С версии: base-4.9.0.0 |
Определено в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> TypeError -> ShowS Исходный код show :: TypeError -> String Исходный код showList :: [TypeError] -> ShowS Исходный код | |
Асинхронные исключения
data SomeAsyncException Исходный код
Суперкласс для асинхронных исключений.
С версии: base-4.7.0.0
Конструкторы
| Exception e => SomeAsyncException e |
Экземпляры
| Exception SomeAsyncException Исходный код | С версии: base-4.7.0.0 |
Определено в GHC.Internal.IO.Exception | |
| Show SomeAsyncException Исходный код | С версии: base-4.7.0.0 |
Определено в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> SomeAsyncException -> ShowS Исходный код show :: SomeAsyncException -> String Исходный код showList :: [SomeAsyncException] -> ShowS Исходный код | |
data AsyncException Исходный код
Асинхронные исключения.
Конструкторы
| StackOverflow | Стек текущего потока превысил установленный предел. Поскольку было возбуждено исключение, стек потока определённо снова окажется ниже предела, однако программисту следует немедленно принять меры для устранения проблемы. |
| HeapOverflow |
Куча программы приближается к установленному пределу, и программе следует принять меры для уменьшения объёма хранящихся в ней активных данных. Примечания:
|
| ThreadKilled | Это исключение возбуждается другим потоком, вызывающим |
| UserInterrupt | По умолчанию это исключение возбуждается в главном потоке программы, когда пользователь запрашивает завершение программы обычным способом (например, нажатием Control-C в консоли). |
Экземпляры
asyncExceptionToException :: Exception e => e -> SomeException Исходный код
С версии: base-4.7.0.0
asyncExceptionFromException :: Exception e => SomeException -> Maybe e Исходный код
С версии: base-4.7.0.0
data NonTermination Исходный код
Возбуждается, когда среда выполнения обнаруживает, что вычисление гарантированно не завершится. Обратите внимание: нет гарантии, что среда выполнения заметит, гарантированно ли завершится то или иное вычисление.
Конструкторы
| NonTermination |
Экземпляры
| Exception NonTermination Исходный код | С версии: base-4.0 |
Определено в GHC.Internal.Control.Exception.Base | |
| Show NonTermination Исходный код | С версии: base-4.0 |
Определено в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> NonTermination -> ShowS Исходный код show :: NonTermination -> String Исходный код showList :: [NonTermination] -> ShowS Исходный код | |
data NestedAtomically Исходный код
Возбуждается, когда программа пытается вызвать atomically из пакета stm внутри другого вызова atomically.
Конструкторы
| NestedAtomically |
Экземпляры
| Exception NestedAtomically Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base | |
| Show NestedAtomically Исходный код | Начиная с: base-4.0 |
Определён в GHC.Internal.Control.Exception.Base МетодыshowsPrec :: Int -> NestedAtomically -> ShowS Исходный код show :: NestedAtomically -> String Исходный код showList :: [NestedAtomically] -> ShowS Исходный код | |
data BlockedIndefinitelyOnMVar Исходный код
Поток заблокирован на MVar, но на MVar больше нет ссылок, поэтому он никогда не сможет продолжить выполнение.
Конструкторы
| BlockedIndefinitelyOnMVar |
Экземпляры
| Exception BlockedIndefinitelyOnMVar Исходный код | Начиная с: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception | |
| Show BlockedIndefinitelyOnMVar Исходный код | Начиная с: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> BlockedIndefinitelyOnMVar -> ShowS Исходный код show :: BlockedIndefinitelyOnMVar -> String Исходный код showList :: [BlockedIndefinitelyOnMVar] -> ShowS Исходный код | |
data BlockedIndefinitelyOnSTM Исходный код
Поток ожидает повторной попытки транзакции STM, но на задействованные TVar больше нет ссылок, поэтому он никогда не сможет продолжить выполнение.
Конструкторы
| BlockedIndefinitelyOnSTM |
Экземпляры
| Exception BlockedIndefinitelyOnSTM Исходный код | Начиная с: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception | |
| Show BlockedIndefinitelyOnSTM Исходный код | Начиная с: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> BlockedIndefinitelyOnSTM -> ShowS Исходный код show :: BlockedIndefinitelyOnSTM -> String Исходный код showList :: [BlockedIndefinitelyOnSTM] -> ShowS Исходный код | |
data AllocationLimitExceeded Исходный код
Этот поток превысил установленный для него лимит выделения памяти. См. setAllocationCounter и enableAllocationLimit.
Начиная с: base-4.8.0.0
Конструкторы
| AllocationLimitExceeded |
Экземпляры
| Exception AllocationLimitExceeded Исходный код | Начиная с: base-4.8.0.0 |
Определён в GHC.Internal.IO.Exception | |
| Show AllocationLimitExceeded Исходный код | Начиная с: base-4.7.1.0 |
Определён в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> AllocationLimitExceeded -> ShowS Исходный код | |
newtype CompactionFailed Исходный код
При уплотнении был обнаружен объект, который нельзя уплотнить. Функции нельзя уплотнять; то же относится к изменяемым объектам и объектам с закреплённым адресом. См. compact.
Начиная с: base-4.10.0.0
Конструкторы
| CompactionFailed String |
Экземпляры
| Exception CompactionFailed Исходный код | Начиная с: base-4.10.0.0 |
Определён в GHC.Internal.IO.Exception | |
| Show CompactionFailed Исходный код | Начиная с: base-4.10.0.0 |
Определён в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> CompactionFailed -> ShowS Исходный код show :: CompactionFailed -> String Исходный код showList :: [CompactionFailed] -> ShowS Исходный код | |
data Deadlock Исходный код
Нет потоков, готовых к выполнению, поэтому программа находится в состоянии взаимной блокировки. Исключение Deadlock возникает только в главном потоке.
Конструкторы
| Deadlock |
Экземпляры
| Exception Deadlock Исходный код | Начиная с: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception МетодыtoException :: Deadlock -> SomeException Исходный код fromException :: SomeException -> Maybe Deadlock Исходный код | |
| Show Deadlock Исходный код | Начиная с: base-4.1.0.0 |
Определён в GHC.Internal.IO.Exception МетодыshowsPrec :: Int -> Deadlock -> ShowS Исходный код show :: Deadlock -> String Исходный код showList :: [Deadlock] -> ShowS Исходный код | |
Выбрасывание исключений
throw :: forall a e. (HasCallStack, Exception e) => e -> a Исходный код
Выбросить исключение. Исключения можно выбрасывать из чистого функционального кода, но перехватывать их можно только в монаде IO.
ВНИМАНИЕ: возможно, вместо этого следует использовать throwIO, чтобы чистый код оставался свободным от исключений.
throwIO :: (HasCallStack, Exception e) => e -> IO a Исходный код
Вариант throw, который можно использовать только в монаде IO.
Хотя throwIO имеет тип, являющийся экземпляром типа throw, эти две функции немного различаются:
throw e `seq` () ===> throw e throwIO e `seq` () ===> ()
Первый пример приведёт к возникновению исключения e, а второй — нет. На самом деле throwIO приведёт к возникновению исключения только при использовании в монаде IO.
Для выбрасывания исключения в монаде IO предпочтительно использовать вариант throwIO, а не throw, поскольку он гарантирует упорядоченность относительно других операций, чего throw не гарантирует. Мы говорим, что throwIO выбрасывает *точные* исключения, а throw, error и другие выбрасывают *неточные* исключения. Например:
throw e + error "boom" ===> error "boom" throw e + error "boom" ===> throw e
оба являются допустимыми редуцированиями, и компилятор может выбрать любое из них (в том числе зациклиться), тогда как
throwIO e >> error "boom" ===> throwIO e
при выполнении всегда выбросит e.
Более подробное техническое введение в то, как GHC выполняет оптимизацию с учётом точных и неточных исключений, см. также на странице вики GHC о точных исключениях.
rethrowIO :: Exception e => ExceptionWithContext e -> IO a Исходный код
Вспомогательная функция для повторного выбрасывания исключений: при повторном выбрасывании новое обратное трассирование не добавляется, но необходимо передать существующий контекст.
ioError :: HasCallStack => IOError -> IO a Исходный код
Выбросить IOError в монаде IO.
throwTo :: Exception e => ThreadId -> e -> IO () Исходный код
throwTo выбрасывает произвольное исключение в целевом потоке (только GHC).
Доставка исключения синхронизируется между исходным и целевым потоками: throwTo не возвращает управление, пока исключение не будет выброшено в целевом потоке. Таким образом, вызывающий поток может быть уверен, что целевой поток получил исключение. Доставка исключений также атомарна относительно других исключений. Атомарность полезна при работе с состояниями гонки: например, если два потока могут завершить друг друга, гарантируется, что только один из потоков сможет завершить другой.
Работа, которую выполнял целевой поток в момент выбрасывания исключения, не теряется: вычисление приостанавливается до тех пор, пока оно не понадобится другому потоку.
Если целевой поток в данный момент выполняет внешний вызов, исключение не будет выброшено (и, следовательно, throwTo не вернёт управление), пока вызов не завершится. Это справедливо независимо от того, находится ли вызов внутри mask. Однако в GHC внешний вызов можно пометить как interruptible; в этом случае throwTo заставит RTS попытаться прервать вызов. Дополнительные сведения см. в документации GHC.
Важное замечание: поведение throwTo отличается от описанного в статье «Asynchronous exceptions in 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.
Аргументы
| :: Exception 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.
catchNoPropagate :: Exception e => IO a -> (ExceptionWithContext e -> IO a) -> IO a Исходный код
Вариант catch, который не аннотирует обработчик исключением, которое было перехвачено. Эту функцию следует использовать при реализации собственных функций обработки ошибок, которые могут повторно выбрасывать исключения.
Если вы повторно выбрасываете исключение без изменений, его следует повторно выбросить с исходным контекстом исключения.
catches :: IO a -> [Handler 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 Handler a Исходный код
Этот тип понадобится при использовании catches.
Экземпляры
| Functor Handler Исходный код | Начиная с: base-4.6.0.0 |
Определён в GHC.Internal.Control.Exception Методыfmap :: (a -> b) -> Handler a -> Handler b Исходный код (<$) :: a -> Handler b -> Handler a Исходный код | |
Аргументы
| :: Exception e | |
| => (e -> Maybe 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 :: Exception e => (e -> IO a) -> IO a -> IO a Исходный код
Вариант catch с переставленными аргументами; полезен в случаях, когда код обработчика короче. Например:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $
...
handleJust :: Exception e => (e -> Maybe b) -> (b -> IO a) -> IO a -> IO a Исходный код
Вариант catchJust с переставленными аргументами (см. handle).
Функции try
try :: Exception e => IO a -> IO (Either e a) Исходный код
Похожа на catch, но возвращает результат типа Either: (Right a), если исключение типа e не возникло, или (Left ex), если возникло исключение типа e и его значение равно ex. Если возникает исключение любого другого типа, оно передаётся следующему внешнему обработчику исключений.
try a = catch (Right `liftM` a) (return . Left)
tryWithContext :: Exception e => IO a -> IO (Either (ExceptionWithContext e) a) Исходный код
Похожа на try, но также возвращает контекст исключения, что полезно, если вы собираетесь повторно выбросить исключение позднее.
tryJust :: Exception e => (e -> Maybe b) -> IO a -> IO (Either b a) Исходный код
Вариант try, принимающий предикат исключения для выбора перехватываемых исключений (ср. catchJust). Если исключение не соответствует предикату, оно выбрасывается повторно.
Функция evaluate
evaluate :: a -> IO a Исходный код
Вычислить аргумент до слабой головной нормальной формы.
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 Исходный код
Эта функция преобразует одно исключение в другое, как предложено в статье «A semantics for imprecise exceptions».
Асинхронные исключения
Асинхронные исключения так называются потому, что возникают под внешним воздействием и могут быть выброшены в любой момент выполнения. StackOverflow и HeapOverflow — два примера системных асинхронных исключений.
Однако основной источник асинхронных исключений — throwTo:
throwTo :: ThreadId -> Exception -> IO ()
throwTo (также killThread) позволяет одному работающему потоку выбросить произвольное исключение в другом потоке. Поэтому относительно целевого потока исключение является асинхронным: в момент его получения поток может выполнять любую работу. С асинхронными исключениями следует обращаться очень осторожно: чрезмерно активное использование throwTo может легко привести к состояниям гонки.
mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Источник
Выполняет вычисление 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_ :: IO a -> IO a Источник
Как mask, но не передаёт аргументу действие restore.
uninterruptibleMask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Источник
Как mask, но вычисление с маскированными исключениями нельзя прервать (см. Control.Exception). ИСПОЛЬЗУЙТЕ ЭТУ ФУНКЦИЮ С БОЛЬШОЙ ОСТОРОЖНОСТЬЮ: если поток, выполняющийся в uninterruptibleMask, по какой-либо причине заблокируется, он (а возможно, и программа, если это главный поток) перестанет отвечать и его нельзя будет завершить. Эта функция должна быть нужна только в том случае, если необходимо маскировать исключения во время прерываемой операции и можно гарантировать, что эта операция будет блокироваться лишь короткое время.
uninterruptibleMask_ :: IO a -> IO a Источник
Как uninterruptibleMask, но не передаёт аргументу действие restore.
data MaskingState Источник
Описывает поведение потока при получении асинхронного исключения.
Конструкторы
| Unmasked | асинхронные исключения разблокированы (обычное состояние) |
| MaskedInterruptible | состояние во время |
| MaskedUninterruptible | состояние во время |
Экземпляры
| Eq MaskingState Источник | Начиная с: base-4.3.0.0 |
Определено в GHC.Internal.IO Методы(==) :: MaskingState -> MaskingState -> Bool Источник (/=) :: MaskingState -> MaskingState -> Bool Источник | |
| Show MaskingState Источник | Начиная с: base-4.3.0.0 |
Определено в GHC.Internal.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 к обработчику исключений
При каждом вызове одной из функций семейства catch обработчик исключений неявно оборачивается в mask. Обычно именно это и нужно: так устраняется распространённая гонка при запуске обработчика исключений, поскольку в момент немедленного возникновения другого исключения в стеке может ещё не быть обработчика, способного его обработать. Однако если при входе в обработчик асинхронные исключения замаскированы, у нас есть время установить новый обработчик исключений до прерывания. Если бы это не было поведением по умолчанию, пришлось бы писать что-то вроде:
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 игнорируется, а результатом становится второй аргумент.
Вспомогательные функции
Аргументы
| :: IO a | вычисление, выполняемое первым («получить ресурс») |
| -> (a -> IO b) | вычисление, выполняемое последним («освободить ресурс») |
| -> (a -> IO c) | промежуточное вычисление |
| -> IO c |
Если нужно получить ресурс, выполнить с ним какую-либо работу, а затем освободить его, рекомендуется использовать bracket, поскольку bracket установит необходимый обработчик исключений, который освободит ресурс, если во время вычисления возникнет исключение. Если исключение возникнет, bracket повторно вызовет его (после освобождения ресурса).
Распространённый пример — открытие файла:
bracket
(openFile "filename" ReadMode)
(hClose)
(\fileHandle -> do { ... })
Аргументы bracket расположены в таком порядке, чтобы функцию можно было частично применить, например:
withFile name mode = bracket (openFile name mode) hClose
Bracket оборачивает действие освобождения в mask, чего достаточно, чтобы действие освобождения завершилось, если оно не вызывает прерываемых действий, даже при наличии асинхронных исключений. Например, hClose не прерывается, если не конкурирует с другими операциями над дескриптором. Аналогичным образом закрытие сокета (из пакета «network») также не прерывается при подобных условиях. Пример прерываемого действия — killThread. Завершение прерываемых действий освобождения можно гарантировать, обернув их в uninterruptibleMask_, но это может лишить программу возможности реагировать на Control-C или тайм-ауты. Другой вариант — выполнить действие освобождения асинхронно в отдельном потоке:
void $ uninterruptibleMask_ $ forkIO $ do { ... }
Ресурс будет освобождён при первой возможности, но поток, вызвавший bracket, не будет блокироваться в непрерываемом состоянии.
bracket_ :: IO a -> IO b -> IO c -> IO c Источник
Вариант 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 Источник
Как finally, но заключительное действие выполняется только в том случае, если вычисление вызвало исключение.
Вывод
displayExceptionWithInfo :: SomeException -> String Источник
Выводит SomeException с дополнительной информацией:
- Тип исходного исключения
- Контекст исключения
По умолчанию uncaughtExceptionHandler использует displayExceptionWithInfo для вывода необработанных исключений. Это поведение по умолчанию можно переопределить с помощью setUncaughtExceptionHandler, например, чтобы показывать пользователю собственные сообщения об ошибках при возникновении исключений.
Начиная с: base-4.22
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/9.14.1/docs/libraries/base-4.22.0.0-66f8/Control-Exception.html