Spec-Zone.ru › Haskell 7

Control.Exception

Copyright (c) The University of Glasgow 2001
License BSD-style (see the file libraries/base/LICENSE)
Maintainer libraries@haskell.org
Stability experimental
Portability non-portable (extended exceptions)
Safe Haskell Trustworthy
Language Haskell2010

Содержание

  • Тип Exception
  • Выбрасывание исключений
  • Перехват исключений
    • Перехват всех исключений
    • Функции catch
    • Функции handle
    • Функции try
    • Функция evaluate
    • Функция mapException
  • Асинхронные исключения
    • Управление асинхронными исключениями
      • Применение mask к обработчику исключений
      • Прерываемые операции
  • Утверждения
  • Вспомогательные функции

Описание

Этот модуль обеспечивает поддержку генерации и перехвата как встроенных, так и определяемых пользователем исключений.

В дополнение к исключениям, выбрасываемым операциями IO, исключения могут быть вызваны чистым кодом (неточными исключениями) или внешними событиями (асинхронными исключениями), но могут быть перехвачены только в монаде IO. Для получения более подробной информации см.:

  • Семантика неточных исключений, Саймон Пейтон Джонс, Алистэр Рид, Тони Хоар, Саймон Марлоу, Фергус Хендерсон, в PLDI'99.
  • Асинхронные исключения в Haskell, Саймон Марлоу, Саймон Пейтон Джонс, Энди Моран и Джон Реппи, в PLDI'01.
  • Расширяемая динамически типизированная иерархия исключений, Саймон Марлоу, в Haskell '06.

Тип Exception

data SomeException Source

Тип SomeException является корнем иерархии типов исключений. Когда выбрасывается исключение типа e, за кулисами оно инкапсулируется в SomeException.

Конструкторы

forall e . Exception e => SomeException e

Экземпляры

Show SomeException
Exception SomeException

class (Typeable e, Show e) => Exception e where Source

Любой тип, который вы хотите выбросить или перехватить как исключение, должен быть экземпляром класса Exception. Простейший случай — новый тип исключения непосредственно под корнем:

data MyException = ThisException | ThatException
    deriving (Show, Typeable)

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
    deriving Typeable

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
    deriving Typeable

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 (Typeable, 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 Source

fromException :: SomeException -> Maybe e Source

displayException :: e -> String Source

Отобразить это значение исключения в удобном для человека формате.

Реализация по умолчанию: show.

Since: 4.8.0.0

Экземпляры

Exception SomeException
Exception ArithException
Exception ErrorCall
Exception IOException
Exception Dynamic
Exception ExitCode
Exception ArrayException
Exception AsyncException
Exception SomeAsyncException
Exception AssertionFailed
Exception AllocationLimitExceeded
Exception Deadlock
Exception BlockedIndefinitelyOnSTM
Exception BlockedIndefinitelyOnMVar
Exception NestedAtomically
Exception NonTermination
Exception NoMethodError
Exception RecUpdError
Exception RecConError
Exception RecSelError
Exception PatternMatchFail
Exception Void

data IOException Source

Исключения, возникающие в монаде IO. IOException записывает более специфичный тип ошибки, описательную строку и, возможно, дескриптор, который использовался, когда ошибка была отмечена.

Экземпляры

Eq IOException
Show IOException
Exception IOException

data ArithException Source

Арифметические исключения.

Конструкторы

Overflow
Underflow
LossOfPrecision
DivideByZero
Denormal
RatioZeroDenominator

С тех пор как: 4.6.0.0

Примеры

Eq ArithException
Ord ArithException
Show ArithException
Exception ArithException

data ArrayException Источник

Исключения, сгенерированные операциями над массивами

Конструкторы

IndexOutOfBounds String

Была предпринята попытка индексирования массива за пределами объявленных границ.

UndefinedElement String

Была предпринята попытка оценить элемент массива, который не был инициализирован.

Примеры

Eq ArrayException
Ord ArrayException
Show ArrayException
Exception ArrayException

data AssertionFailed Источник

assert было применено к False.

Конструкторы

AssertionFailed String

Примеры

Show AssertionFailed
Exception AssertionFailed

data SomeAsyncException Источник

Базовый класс для асинхронных исключений.

С тех пор как: 4.7.0.0

Конструкторы

forall e . Exception e => SomeAsyncException e

Примеры

Show SomeAsyncException
Exception SomeAsyncException

data AsyncException Источник

Асинхронные исключения.

Конструкторы

StackOverflow

Стек текущего потока превысил свой лимит. Поскольку исключение было вызвано, стек потока, безусловно, будет ниже его лимита снова, но программист должен немедленно принять корректирующие меры.

HeapOverflow

Купа программы достигает своего предела, и программа должна предпринять действия, чтобы уменьшить количество живых данных. Примечания:

  • Не определено, какой поток получит это исключение.
  • В настоящее время GHC не генерирует HeapOverflow исключения.
ThreadKilled

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

UserInterrupt

Это исключение вызывается по умолчанию в главном потоке программы, когда пользователь запрашивает завершение программы с помощью обычного механизма (например, Control-C в консоли).

Примеры

Eq AsyncException
Ord AsyncException
Show AsyncException
Exception AsyncException

asyncExceptionToException :: Exception e => e -> SomeException Источник

С тех пор как: 4.7.0.0

asyncExceptionFromException :: Exception e => SomeException -> Maybe e Источник

С тех пор как: 4.7.0.0

data NonTermination Источник

Вызывается, когда среда выполнения обнаруживает, что вычисление гарантированно не завершится. Обратите внимание, что нет гарантии, что среда выполнения заметит, гарантировано ли завершение любого данного вычисления или нет.

Конструкторы

NonTermination

Примеры

Show NonTermination
Exception NonTermination

data NestedAtomically Источник

Вызывается, когда программа пытается вызвать atomically, из пакета stm, внутри другого вызова atomically.

Конструкторы

NestedAtomically

Примеры

Show NestedAtomically
Exception NestedAtomically

data ЗаблокированБессрочноНаMVar Source

Поток заблокирован на MVar, но других ссылок на MVar нет, поэтому он никогда не сможет продолжить.

Конструкторы

ЗаблокированБессрочноНаMVar

Типы

Show ЗаблокированБессрочноНаMVar
Исключение ЗаблокированБессрочноНаMVar

data ЗаблокированБессрочноНаSTM Source

Поток ожидает повторной попытки транзакции STM, но других ссылок на любые TVar нет, поэтому он никогда не сможет продолжить.

Конструкторы

ЗаблокированБессрочноНаSTM

Типы

Show ЗаблокированБессрочноНаSTM
Исключение ЗаблокированБессрочноНаSTM

data ПревышенПределВыделения Source

Данный поток превысил свой лимит выделения. См. setAllocationCounter и enableAllocationLimit.

С версии: 4.8.0.0

Конструкторы

ПревышенПределВыделения

Типы

Show ПревышенПределВыделения
Исключение ПревышенПределВыделения

data Тупик Source

Нет запустимых потоков, поэтому программа находится в тупике. Исключение Deadlock возникает только в главном потоке.

Конструкторы

Тупик

Типы

Show Тупик
Исключение Тупик

data ОшибкаОтсутствияМетода Source

Вызван метод класса без определения (ни стандартного, ни определения в соответствующем типе). String содержит информацию о вызываемом методе.

Конструкторы

ОшибкаОтсутствияМетода Строка

Типы

Show ОшибкаОтсутствияМетода
Исключение ОшибкаОтсутствияМетода

data ОшибкаСопоставленияШаблонов Source

Не удалось выполнить сопоставление шаблонов. String содержит информацию о местоположении шаблона в исходном коде.

Конструкторы

ОшибкаСопоставленияШаблонов Строка

Типы

Show ОшибкаСопоставленияШаблонов
Исключение ОшибкаСопоставленияШаблонов

data ОшибкаКонструктораЗаписи Source

Использовано неинициализированное поле записи. String содержит информацию о местоположении создания записи в исходном коде.

Конструкторы

ОшибкаКонструктораЗаписи Строка

Типы

Show ОшибкаКонструктораЗаписи
Исключение ОшибкаКонструктораЗаписи

data ОшибкаСелектораЗаписи Source

Селектор записи был применен к конструктору без соответствующего поля. Это возможно только с типом данных, имеющим несколько конструкторов, где некоторые поля присутствуют в одном, но отсутствуют в другом. String содержит информацию о местоположении селектора записи в исходном коде.

Конструкторы

ОшибкаСелектораЗаписи Строка

Типы

Show ОшибкаСелектораЗаписи
Исключение ОшибкаСелектораЗаписи

data ОшибкаОбновленияЗаписи Source

Обновление записи было выполнено на конструкторе без соответствующего поля. Это возможно только с типом данных, имеющим несколько конструкторов, где некоторые поля присутствуют в одном, но отсутствуют в другом. String содержит информацию о местоположении обновления записи в исходном коде.

Конструкторы

ОшибкаОбновленияЗаписи Строка

Типы

Show ОшибкаОбновленияЗаписи
Исключение ОшибкаОбновленияЗаписи

newtype ОшибкаВызова Source

Это выбрасывается, когда пользователь вызывает error. String — аргумент, переданный error.

Конструкторы

ErrorCall String

Примеры

Eq ErrorCall
Ord ErrorCall
Show ErrorCall
Исключение ErrorCall

Выбрасывание исключений

throw :: Исключение 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 :: IOError -> IO a Источник

Выбросить IOError в IO монаде.

throwTo :: Исключение e => ThreadId -> 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, за исключением того, что исключение выбрасывается как асинхронное исключение. Это означает, что если есть окружающее чистое вычисление, что имеет место, если текущая операция ввода-вывода находится внутри 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) ...)

Однако, обычно этого делать не нужно!

Например, предположим, что вы хотите прочитать файл, но если он не существует, продолжить, как если бы он содержал "". Вы можете быть искушены просто перехватить все исключения и вернуть "" в обработчике. Однако это имеет множество нежелательных последствий. Например, если пользователь нажмет Ctrl+C в подходящий момент, исключение UserInterrupt будет перехвачено, и программа продолжит работу, считая, что файл содержит "". Аналогично, если другой поток попытается убить поток, читающий файл, исключение ThreadKilled будет проигнорировано.

Вместо этого вы должны перехватывать только те исключения, которые вам действительно нужны. В этом случае это, вероятно, будет более конкретно, чем просто «любое исключение ввода-вывода»; ошибки разрешения, скорее всего, также должны обрабатываться по-разному. Вместо этого вы, вероятно, захотите что-то вроде:

e <- tryJust (guard . isDoesNotExistError) (readFile f)
let str = either (const "") id e

Иногда вам действительно нужно перехватить любой тип исключения. Однако в большинстве случаев это делается для того, чтобы выполнить некоторую очистку; вас не интересует само исключение. Например, если вы открываете файл, вам нужно снова закрыть его, независимо от того, выполняется обработка файла нормально или выбрасывается исключение. Однако в этих случаях вы можете использовать функции, такие как bracket, finally и onException, которые никогда не передают вам исключение, а просто вызывают функции очистки в соответствующие моменты.

Но иногда вам действительно нужно перехватить любое исключение и увидеть, что это за исключение. Один пример — в самом верхнем уровне программы, вы можете захотеть перехватить любое исключение, напечатать его в журнал или на экран, а затем корректно выйти. В этих случаях вы можете использовать catch (или одну из других функций перехвата исключений) с типом SomeException.

Функции catch

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 -> [Handler a] -> IO a Source

Иногда вам нужно поймать два разных типа исключений. Вы можете сделать что-то вроде

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 Source

Вам это понадобится при использовании catches.

Конструкторы

forall e . Exception e => Handler (e -> IO a)

Примеры

Functor Handler

catchJust Source

Аргументы

:: 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 Source

Вариант catch с переставленными аргументами; полезно в ситуациях, когда код обработчика короче. Например:

  do handle (\NonTermination -> exitWith (ExitFailure 1)) $
     ...

handleJust :: Exception e => (e -> Maybe b) -> (b -> IO a) -> IO a -> IO a Source

Вариант catchJust с переставленными аргументами (см. handle).

Функции try

try :: Exception e => IO a -> IO (Either e a) Source

Аналогично 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 :: a -> IO a Source

Принудительно вычисляет свой аргумент до слабой нормальной формы при выполнении результирующего IO действия. Может быть использован для упорядочения вычислений относительно других IO операций; его семантика задаётся

  evaluate x `seq` y    ==>  y
  evaluate x `catch` f  ==>  (return $! x) `catch` f
  evaluate x >>= f      ==>  (return $! x) >>= f

Примечание: первое уравнение подразумевает, что (evaluate x) не равно (return $! x). Правильное определение

  evaluate x = (return $! x) >>= return

Функция 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 ... . Это особенно полезно, если вам нужно установить обработчик исключений в порождённом потоке до получения любых асинхронных исключений. Чтобы создать новый поток в незамаскированном состоянии, используйте forkIOUnmasked.

mask_ :: IO a -> IO a Source

Как mask, но не передает действие restore аргументу.

uninterruptibleMask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Source

Как mask, но замаскированное вычисление не прерывается (см. Control.Exception). ЭТО НУЖНО ИСПОЛЬЗОВАТЬ С ОСТОРОЖНОСТЬЮ, потому что если поток, выполняющийся в uninterruptibleMask, блокируется по какой-либо причине, то поток (и, возможно, программа, если это главный поток) будет неотзываемым и неуничтожимым. Эта функция должна потребоваться только в том случае, если вам нужно замаскировать исключения вокруг прерываемой операции, и вы можете гарантировать, что прерываемая операция будет блокироваться только на короткий промежуток времени.

uninterruptibleMask_ :: IO a -> IO a Source

Как uninterruptibleMask, но не передает действие restore аргументу.

data MaskingState Source

Описывает поведение потока при получении асинхронного исключения.

Конструкторы

Unmasked

асинхронные исключения не замаскированы (обычное состояние)

MaskedInterruptible

состояние во время mask: асинхронные исключения замаскированы, но блокирующие операции все еще могут прерываться

MaskedUninterruptible

состояние во время uninterruptibleMask: асинхронные исключения замаскированы, и блокирующие операции не могут прерываться

Примеры

Eq MaskingState
Show MaskingState

getMaskingState :: IO MaskingState Source

Возвращает MaskingState для текущего потока.

allowInterrupt :: IO () Source

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

При вызове вне mask, или внутри uninterruptibleMask, эта функция не оказывает никакого влияния.

Since: 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, forkIOUnmasked, myThreadId

Утверждения

assert :: Bool -> a -> a Source

Если первый аргумент оценивается как True, то результатом является второй аргумент. В противном случае поднимается исключение AssertionFailed, содержащее String с именем файла и номером строки вызова assert.

Утверждения обычно можно включить или выключить с помощью флага компилятора (для GHC утверждения обычно включены, если оптимизация не включена с флагом -O или установлен параметр -fignore-asserts). Когда утверждения выключены, первый аргумент к assert игнорируется, а второй аргумент возвращается как результат.

Утилиты

bracket Source

Аргументы

:: 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_ :: IO a -> IO b -> IO c -> IO c Source

Вариант bracket, где возвращаемое значение от первого вычисления не требуется.

bracketOnError Source

Аргументы

:: IO a

вычисление, которое выполняется в первую очередь ("получение ресурса")

-> (a -> IO b)

вычисление, которое выполняется последним ("освобождение ресурса")

-> (a -> IO c)

вычисление, которое выполняется между ними

-> IO c

Подобно bracket, но выполняет только последнее действие, если в промежуточном вычислении возникло исключение.

finally Source

Аргументы

:: 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/7.10.3/docs/html/libraries/base-4.8.2.0/Control-Exception.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API