finally
和onException
是模块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
答案 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中处理异常时,请务必谨慎评估何时进行处理,以确保在异常处理程序的动态范围内引发异常,并且不会由于延迟评估而延迟该异常。