Haskell中的异常处理

83

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

  • try (Control.Exception.try :: Exception e => IO a -> IO (Either e a))
  • catch (Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a)
  • handle (Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a)

我需要知道以下几点:

  1. 我该在什么时候使用哪个函数?
  2. 如何使用这些函数并附上一些简单的例子?
  3. catch和handle有什么区别?它们具有几乎相同的签名,只是顺序不同。

我将尝试写下我的尝试,希望你能帮助我:

try

我有一个例子:

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

我有两个问题:

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

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

catch/try

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

3个回答

148

什么时候使用哪个函数?

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

  • 如果你想在出现异常时做一些清理工作,请使用 finallybracketonException
  • 要在出现异常后进行恢复并执行其他操作,最好的选择是使用 try 系列之一。
  • ... 除非您正在从异步异常中恢复,这时请使用 catchcatchJust

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

try 接受一个要运行的 IO 操作,并返回一个 Either。如果计算成功,结果将被包装在 Right 构造器中。(Think right as opposed to wrong)。如果操作抛出了指定类型的异常,则将其返回到 Left 构造器中。如果异常不是适当的类型,则继续向上传播。将 SomeException 指定为类型会捕获所有异常,这可能是好也可能是坏。

请注意,如果您想捕获来自纯计算的异常,您必须使用 evaluatetry 中强制求值。

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

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 => (e -> IO a) -> IO a -> 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单子中)。如果您需要在纯代码中处理错误,则应考虑改用返回使用MaybeEither的值(或其他某种代数数据类型)。这通常更可取,因为它更明确,所以您始终知道何时会发生什么。像Control.Monad.Error这样的单子使这种类型的错误处理更易于处理。


另请参见:


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"

为了做到这一点,您必须使用ScopedTypeVariables GHC扩展,但我认为从审美角度来看这是值得的。

1

关于问题3:catch和handle是相同的(通过hoogle发现)。使用哪个通常取决于每个参数的长度。如果操作较短,请使用catch,反之亦然。以下是文档中的简单handle示例:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接