Haskell中的异常处理


79

我需要帮助以了解三个Haskell函数的用法

  • 试试(Control.Exception.try :: Exception e => IO a -> IO (Either e a)
  • 赶上(Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a
  • 处理(Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a

我需要知道几件事:

  1. 什么时候使用哪个功能?
  2. 如何通过一些简单的示例使用此功能?
  3. 捕获和处理之间的区别在哪里?它们具有几乎相同的签名,只是顺序不同。

我将尝试写下我的尝试,并希望您能为我提供帮助:

尝试

我有一个像这样的例子:

x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())

我有两个问题:

  1. 如何设置自定义错误输出?

  2. 我该怎么做才能将所有错误设置为SomeException,所以我不必编写 :: IO (Either SomeException())

抓住/尝试

您能给我看一个带有自定义错误输出的简短示例吗?


Answers:


132

什么时候使用哪个功能?

这是Control.Exception文档中的建议:

  • 如果你想要做一些清理工作的事件将引发异常,使用finallybracketonException
  • 要在发生异常后恢复健康并做其他事情,最好的选择是使用其中一个try家庭。
  • ...除非您要从异步异常中恢复,否则请使用catchcatchJust

尝试:: Exception e => IO a-> IO(ea要么)

try采取IO措施运行,并返回Either。如果计算成功,则将结果包装在Right构造函数中。(思考对与错)。如果操作引发了指定类型的异常,则将其在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 ::例外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)中断。引发异步异常的尝试将阻塞,直到处理程序完成运行为止。

请注意,catchPrelude中有一个不同之处,因此您可能需要这样做import Prelude hiding (catch)

处理::异常e =>(e-> IO a)-> IO a-> IO a

handle只是catch带有相反顺序的参数。使用哪种代码取决于什么使您的代码更具可读性,或者哪种代码更适合您要使用部分应用程序的情况。它们在其他方面是相同的。

尝试,捕获和处理

请注意trycatchhandle将捕获指定/推断类型的所有异常。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

纯度说明

请注意,这种类型的异常处理只能在不纯代码(例如IOmonad)中发生。如果需要处理纯代码中的错误,则应使用MaybeEither代替(或其他代数数据类型)研究返回值。这通常是可取的,因为它更明确,因此您始终知道在什么地方会发生什么。之类的MonadControl.Monad.Error使此类错误处理更易于使用。


也可以看看:


8
内容丰富,但令您惊讶的是,您没有从Control.Exception文档中删除经验法则。即“使用try,除非您要从异步异常中恢复,在这种情况下,请使用catch
John L


2

我看到一件事也让您感到烦恼(您的第二个问题)是我的写作, :: IO (Either SomeException ())这也使我感到烦恼。

我现在从此更改了一些代码:

let x = 5 `div` 0
result <- try (print x) :: IO (Either SomeException ())
case result of
    Left _ -> putStrLn "Error"
    Right () -> putStrLn "OK"

对此:

let x = 5 `div` 0
result <- try (print x)
case result of
    Left (_ :: SomeException) -> putStrLn "Error"
    Right () -> putStrLn "OK"

为此,您必须使用ScopedTypeVariablesGHC扩展名,但从美学角度来看,这是值得的。


1

回复:问题3:捕获和处理是相同的(通过hoogle找到)。使用哪种选择通常取决于每个参数的长度。如果动作较短,请使用catch,反之亦然。文档中的简单句柄示例:

do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...

同样,可以想象,您可以使用handle函数来创建一个自定义处理程序,然后可以将其传递给其他人。(根据文档改编):

let handler = handle (\NonTermination -> exitWith (ExitFailure 1))

自定义错误消息:

do       
    let result = 5 `div` 0
    let handler = (\_ -> print "Error") :: IOException -> IO ()
    catch (print result) handler
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.