onException的行为

时间:2018-08-30 01:36:55

标签: haskell exception

finallyonException是模块Control.Exception中的两个函数,它们具有相同的签名但行为不同。  Here是文档。 对于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的{​​{3}}之后,我尝试如下进行测试:

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 :(得分:10)

您被懒惰的评价所咬住。

throwIO的主要保证之一是,它保证何时会在执行其他IO动作时引发异常。来自the documentation

  

尽管throwIO的类型是throw类型的实例,但两个函数有细微的不同:

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

第一个示例将引发异常e,而第二个示例则不会。实际上,throwIO仅会在IO单子中使用时引发异常。 throwIO变体应优先使用,以引发IO monad内的异常,因为它保证相对于其他IO操作的排序,而throw则不能。

这意味着,当执行throwIO e动作(不仅仅是评估!)作为onException产生的动作执行的一部分时,可以保证确实引发异常。由于在异常处理程序执行的动态范围内引发了异常,因此可以检测到异常并执行处理程序功能。

但是,当您编写return e时,它执行的操作并不会在执行时将e评估为WHNF,e仅在以下情况下评估: /当评估行动的结果本身时。在GHCi通过div 4 0强制执行动作的结果来强制show表达式时,控件已离开了onException执行的动态范围,并且它安装的处理程序不再处于打开状态。堆栈。引发了异常,但为时已晚。

要获得所需的行为,至关重要的是要确保在执行操作的过程中(而不是前后)评估div 4 0。这就是the evaluate function from Control.Exception的目的。它会在执行IO动作本身的过程中评估其对WHNF的参数,从而保证周围评估异常处理程序可以检测到作为该评估的一部分而引发的任何异常:

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

道义:在Haskell中处理异常时,请务必谨慎评估何时进行处理,以确保在异常处理程序的动态范围内引发异常,并且不会由于延迟评估而延迟该异常。