haskell - `onException` 的行为方式

标签 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 , 它说:

Like finally, but only performs the final action if there was an exception raised by the computation.



所以我做了以下测试:
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!"当引发异常时。

检查后source code对于 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

最佳答案

你被懒惰的评估所困扰。 throwIO的关键保证之一是它保证何时会针对其他 IO 的执行引发异常行动。来自 the documentation :

Although throwIO has a type that is an instance of the type of throw, the two functions are subtly different:

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

The first example will cause the exception e to be raised, whereas the second one won't. In fact, throwIO will only cause an exception to be raised when it is used within the IO monad. The throwIO variant should be used in preference to throw to raise an exception within the IO monad because it guarantees ordering with respect to other IO operations, whereas throw does not.


这意味着,当 throwIO e Action 作为 onException 产生的 Action 执行的一部分被执行(不仅仅是评估!) ,保证实际引发异常。由于异常是在异常处理程序执行的动态范围内引发的,因此检测到异常并执行处理程序函数。
然而,当你写 return e ,它产生的 Action 不计算 e WHNF 执行时,e仅当/当 Action 的结果本身被评估时才被评估。到了div 4 0表达式由 GHCi 强制 show获取操作的结果,控制权已离开 onException 执行的动态范围,并且它安装的处理程序不再在堆栈上。引发了异常,但引发得太晚了。
要获得您想要的行为,确保您评估 div 4 0 至关重要。作为行动执行的一部分,而不是之前或之后的片刻。这就是 the evaluate function from Control.Exception 是为了。作为 IO 执行的一部分,它将评估其对 WHNF 的参数。 Action 本身,保证作为评估的一部分引发的任何异常都将被周围的异常处理程序检测到:
ghci> onException (evaluate $ div 4 0) (putStrLn "Oops!")
Oops!
*** Exception: divide by zero
寓意:在 Haskell 中处理异常时,在评估事物时要非常小心,以确保在异常处理程序的动态范围内引发异常,并且不会由于延迟评估而延迟。

关于haskell - `onException` 的行为方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52088100/

相关文章:

exception - try catch/except finally 语句中的finally有什么意义

c# - 为什么异常总是被接受为返回类型(抛出时)?

JAVA java.util.ConcurrentModificationException :null Exception

c# - 如何在不丢失 C# 堆栈跟踪的情况下重新抛出 InnerException?

java - 如何将这个简单的 OOP 程序转换为函数式编程语言?

haskell - 使用元组列表

haskell - 比较整数值和浮点值

haskell - 在 Haskell ("second order Haskell"中生成 Haskell 类型的工具?

java - 将异常放在自己的子包中?

haskell - 无法将预期类型 `Int' 与实际类型 `m0 Int' 匹配