Haskell中的异常处理

时间:2011-05-15 15:38:21

标签: haskell exception-handling

我需要帮助来理解三个Haskell函数的用法

  • 尝试(Control.Exception.try :: Exception e => IO a -> IO (Either e a)
  • catch(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. 捕获和处理之间的区别在哪里?他们只有不同的订单才有相同的签名。
  4. 我会写下我的试验,希望你能帮助我:

    尝试

    我有一个例子:

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

    我有两个问题:

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

    2. 如何将所有错误设置为SomeException,以便我不必编写:: IO (Either SomeException())

    3. 捕获/尝试

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

4 个答案:

答案 0 :(得分:122)

我何时使用哪个功能?

以下是Control.Exception文档的建议:

  • 如果您想在引发异常时进行一些清理,请使用finallybracketonException
  • 要在异常后恢复并执行其他操作,最好选择使用try系列之一。
  • ...除非您正在从异步异常中恢复,在这种情况下使用catchcatchJust

try :: Exception e => IO a - > IO(e e)

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 :: Exception e =&gt; IO a - &gt; (e - &gt; IO a) - &gt; IO a

catchtry类似。它首先尝试运行指定的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 =&gt; (e - &gt; IO a) - &gt; IO a - &gt; IO a

handle只是catch,其参数的顺序相反。使用哪一个取决于使您的代码更具可读性的原因,或者如果您想使用部分应用程序,哪一个更适合您。它们在其他方面是相同的。

tryJust,catchJust和handleJust

请注意,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

关于纯度的说明

请注意,此类异常处理只能在不纯的代码中发生(即IO monad)。如果您需要处理纯代码中的错误,则应该使用MaybeEither(或其他一些代数数据类型)来查看返回值。这通常是可取的,因为它更明确,所以你总是知道在哪里会发生什么。像Control.Monad.Error这样的Monads使这种类型的错误处理更容易使用。


另见:

答案 1 :(得分:5)

Edward Z. Yang在haskell中有一篇关于异常处理的文章:8 ways to report errors in Haskell revisited

答案 2 :(得分:1)

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

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

另外,你可以想象一下咖喱手柄功能来制作一个自定义处理程序,你可以传递它,例如。 (改编自文件):

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

自定义错误消息:

do       
    let result = 5 `div` 0
    let handler = (\_ -> print "Error") :: IOException -> IO ()
    catch (print result) handler

答案 3 :(得分:1)

我发现有一件事也让你烦恼(你的第二个问题)是:: 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"

要做到这一点,你必须使用ScopedTypeVariables GHC扩展,但我认为在美学上它是值得的。