什么时候使用哪个函数?
以下是来源于 Control.Exception 文档的建议:
- 如果你想在出现异常时做一些清理工作,请使用
finally
、bracket
或 onException
。
- 要在出现异常后进行恢复并执行其他操作,最好的选择是使用
try
系列之一。
- ... 除非您正在从异步异常中恢复,这时请使用
catch
或 catchJust
。
try :: Exception e => IO a -> IO (Either e a)
try
接受一个要运行的 IO
操作,并返回一个 Either
。如果计算成功,结果将被包装在 Right
构造器中。(Think right as opposed to wrong)。如果操作抛出了指定类型的异常,则将其返回到 Left
构造器中。如果异常不是适当的类型,则继续向上传播。将 SomeException
指定为类型会捕获所有异常,这可能是好也可能是坏。
请注意,如果您想捕获来自纯计算的异常,您必须使用 evaluate
在 try
中强制求值。
main = do
result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)
case result of
Left ex -> putStrLn $ "Caught exception: " ++ show ex
Right val -> putStrLn $ "The answer was: " ++ show val
catch :: Exception e => IO a -> (e -> IO a) -> IO a
catch
与try
类似。它首先尝试运行指定的IO
操作,但如果抛出异常,则将异常传递给处理程序以获取替代答案。
main = catch (print $ 5 `div` 0) handler
where
handler :: SomeException -> IO ()
handler ex = putStrLn $ "Caught exception: " ++ show ex
然而,这里有一个重要的区别。使用catch
时,您的处理程序无法被异步异常(即通过throwTo
从另一个线程抛出的异常)中断。试图引发异步异常将阻塞,直到您的处理程序完成运行。
请注意,Prelude中有一个不同的catch
,因此您可能希望执行import Prelude hiding (catch)
。
handle :: Exception e => (e -> IO a) -> IO a -> IO a
handle
只是将参数反转的catch
。使用哪个取决于什么使您的代码更易读,或者哪个适合部分应用程序。否则它们是相同的。
tryJust、catchJust和handleJust
请注意,try
、catch
和handle
将捕获指定/推断类型的全部异常。 tryJust
和相关函数允许您指定选择器函数,过滤出您特别想要处理的异常。例如,所有算术错误都属于ArithException
类型。如果您只想捕获DivideByZero
,可以执行以下操作:
main = do
result <- tryJust selectDivByZero (evaluate $ 5 `div` 0)
case result of
Left what -> putStrLn $ "Division by " ++ what
Right val -> putStrLn $ "The answer was: " ++ show val
where
selectDivByZero :: ArithException -> Maybe String
selectDivByZero DivideByZero = Just "zero"
selectDivByZero _ = Nothing
关于纯度的说明
请注意,这种类型的异常处理只能发生在非纯代码中(即IO
单子中)。如果您需要在纯代码中处理错误,则应考虑改用返回使用Maybe
或Either
的值(或其他某种代数数据类型)。这通常更可取,因为它更明确,所以您始终知道何时会发生什么。像Control.Monad.Error
这样的单子使这种类型的错误处理更易于处理。
另请参见: