哈斯克尔的例外处理

我需要帮助来理解三个 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. 我能做些什么来设置所有的错误到一些例外,所以我不必写的 :: IO (Either SomeException())

抓住/尝试

你能给我一个简短的例子与自定义错误输出?

37960 次浏览

回答: 问题3: catch 和 handle 是 一样(通过 胡格尔找到)。选择使用哪个参数通常取决于每个参数的长度。如果动作较短,则使用 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

什么时候使用哪个函数?

下面是 Control 的建议。异常文档:

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

例外情况 e = > IO a-> IO (要么 e a)

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

异常 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从另一个线程抛出)中断。引发异步异常的尝试将被阻止,直到处理程序完成运行。

请注意,有一个不同的 catch在序曲,所以你可能要做的 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

纯洁的笔记

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


参见:

我发现有一件事情也让你很恼火(你的第二个问题) ,那就是 :: 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 扩展,但我认为从审美角度来看,这是值得的。