Как ведет себя `onException` - PullRequest
       0

Как ведет себя `onException`

0 голосов
/ 30 августа 2018

finally и onException - две функции из модуля Control.Exception, которые имеют одинаковую сигнатуру, но ведут себя по-разному. Здесь - это документ. Для finally написано:

finally 
:: IO a  -- computation to run first
-> IO b  -- computation to run afterward (even if an exception was raised)
-> IO a

, а для onException написано:

Как и finally, но выполняет только последнее действие, если исключение вызвано вычислением.

Итак, я делаю следующий тест:

ghci> finally (return $ div 4 2) (putStrLn "Oops!")
Oops!
2
ghci> finally (return $ div 4 0) (putStrLn "Oops!")
Oops!
*** Exception: divide by zero

, который действует как ожидалось.

Однако onException не:

ghci> onException (return $ div 4 2) (putStrLn "Oops!")
2
ghci> onException (return $ div 4 0) (putStrLn "Oops!")  -- does not act as expected
*** Exception: divide by zero

Как описано выше, onException выполняет окончательное действие только в случае возникновения исключения, но приведенный выше пример показывает, что onException не выполняет окончательное действие, т.е. putStrLn "Oops!" при возникновении исключения.

После проверки исходного кода для onException я пытаюсь выполнить тест следующим образом:

ghci> throwIO (SomeException DivideByZero) `catch` \e -> do {_ <- putStrLn "Oops!"; throwIO (e :: SomeException)}
Oops!
*** Exception: divide by zero
ghci> onException (throwIO (SomeException DivideByZero)) (putStrLn "Oops!")
Oops!
*** Exception: divide by zero

Как видно, когда явное исключение возникло, было выполнено последнее действие.

Таким образом, вопрос return $ div 4 0 действительно вызывает исключение, но почему onException (return $ div 4 0) (putStrLn "Oops!") не выполняет окончательное действие putStrLn "Oops!"? Что мне не хватает? А как было выполнено исключение?

ghci> throwIO (SomeException DivideByZero)
*** Exception: divide by zero
ghci> (return $ div 4 0) :: IO Int
*** Exception: divide by zero

1 Ответ

0 голосов
/ 30 августа 2018

Вас укусила ленивая оценка.

Одна из ключевых гарантий throwIO заключается в том, что она гарантирует , когда будет вызвано исключение в отношении выполнения других IO действий. С документация :

Хотя throwIO имеет тип, который является экземпляром типа throw, две функции слегка различаются:

throw e   `seq` x  ===> throw e
throwIO e `seq` x  ===> x

Первый пример вызовет исключение e, а второй - нет. Фактически, throwIO будет вызывать исключение, только когда оно используется в монаде IO. Вариант throwIO следует использовать вместо броска, чтобы вызвать исключение в монаде IO, поскольку он гарантирует упорядочение относительно других операций IO, тогда как throw - нет.

Это означает, что когда действие throwIO e выполняется (а не просто оценивается!) Как часть выполнения действия, производимого onException, гарантированно возникает исключение. Поскольку исключение возникает в динамическом экстенте выполнения обработчика исключений, исключение обнаруживается и выполняется функция обработчика.

Однако, когда вы пишете return e, выполняемое им действие не оценивает e в WHNF при его выполнении, а e оценивается, только если / когда результат действия сам по себе оценены. К тому моменту, когда выражение div 4 0 будет принудительно введено GHCi с помощью show результата операции, элемент управления оставит динамический экстент выполнения onException, и установленный обработчик больше не будет в стеке. Возникает исключение, но оно поднимается слишком поздно.

Чтобы получить желаемое поведение, важно убедиться, что вы оцениваете div 4 0 как часть выполнения вашего действия, а не мгновение до или после. Для этого функция evaluate из Control.Exception. Он оценивает свой аргумент WHNF как часть выполнения самого действия IO, гарантируя, что любые исключения, возникшие в ходе этой оценки, будут обнаружены окружающим обработчиком исключений:

ghci> onException (evaluate $ div 4 0) (putStrLn "Oops!")
Oops!
*** Exception: divide by zero

Мораль: при обработке исключений в Haskell будьте очень осторожны с оценками, чтобы исключение возникало в динамическом экстенте вашего обработчика исключений и не откладывалось из-за отложенной оценки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...