无法使用Control.Exception.try捕获“Prelude.read:no parse”异常

时间:2013-11-04 13:24:57

标签: haskell

我试图从文件中读取一些值并捕获可能发生的每个异常(在"Easier to ask for forgiveness than permission"的思维模式中)。我在捕获Prelude.read: no parse异常时遇到问题。要告诉try它应该捕获每个异常,我使用显式类型tryAny定义SomeException,据我所知,这是每个异常的“超类型”:

import Control.Exception (try,SomeException)
tryAny :: IO a -> IO (Either SomeException a)
tryAny = try

使用tryAny我似乎能够捕获IO错误:

> tryAny (fromFile "nonExistingFileName")
Left nonExistingFileName: openFile: does not exist (No such file or directory)

但是不会捕获读取错误:

> tryAny (return ((read "a")::Int))
Right *** Exception: Prelude.read: no parse

我能做些什么来捕捉每一个例外?

4 个答案:

答案 0 :(得分:4)

return不评估其参数,因此不会抛出任何异常。当您尝试打印结果时,评估发生在tryAny之外。

使用evaluate(可能与force的{​​{1}}一起,具体取决于您的实际情况)。

答案 1 :(得分:2)

经过批次尝试evaluateforcetry后,按照@Roman Cheplyaka的建议,我想出了这个解决方案现在我看到了最终的结果,这看起来几乎非常明显:

import Control.DeepSeq (force,NFData)
import Control.Exception (try,SomeException)

tryAnyStrict :: NFData a => IO a -> IO (Either SomeException a)
tryAnyStrict = try.evaluateIoCompletely

evaluateIoCompletely :: NFData a => IO a -> IO a
evaluateIoCompletely ioA = do
    a <- ioA
    evaluate $ force a

此解决方案将进程拆分为一个函数,该函数将强制执行严格的评估并抛出所有异常以及将捕获这些异常的包装器。 force返回一个纯粹的haskell对象,该对象本身必须首先进行评估,以强制对a进行深入评估。来自documentation

force x = x `deepseq` x
     

强制x完全计算x,然后返回它。注意,力x仅在需要力x本身的值时执行评估,因此基本上它将浅评估转变为深度评估。

在我之前的尝试中,这个实现有一些细微的问题:

  • 如果您直接使用ioA作为force而不是a的输入,那么您最终会得到IO (IO a)类型的对象,因为evaluate具有输入b -> IO b。这个直接问题可以通过类型IO(IO a) -> IO a的一些解包函数来解决,但是你会遇到NFData a的类型类假设的问题,现在需要NFData (IO a)IO a。但对于a::(String,String),似乎没有可用的实例声明,至少对于tryAnyStrict = do a <- ioA; (try.evaluate.force) a
  • 您可以使用a <- ioA而不是使用两个功能。那么问题是ioA引起的异常不会被捕获,例如serialized <- readFile; return $ read serializedtry.evaluateIoCompletely的结果,并且要读取的文件不存在。
  • 如果没有类型信息,您无法在代码中内联tryAnyStrict,因为尝试需要知道它应该捕获哪个异常。现在,此信息通过e的返回类型提供,该类型将try :: Exception e => IO a -> IO (Either e a)的{​​{1}}类型解析为最常见的例外类型SomeException

现在我tryAnyStrict工作后我仍然遇到延迟评估的问题,因为如果a <- ioA失败 - 其中ioA是read对字符串懒惰地从文件中读取的结果 - (例如,文件内容没有read所需的格式),那么文件仍未完全读取,因此仍然打开。对文件的后续写入将失败(“openFile:resource busy”)。所以......我实际上可能会使用@kqr和@Roman Cheplyaka建议的readMayreadMaybe重新编写整个代码,并强制严格读取文件。

答案 2 :(得分:1)

我有同样的问题,并且没有像某些评论中所建议的那样找到readMaybe。但我找到了另一个有用的阅读变体来解决我的问题 - readIO

  

readIO函数类似于read,除了它表示解析IO monad的失败而不是终止程序。

使用它我能够捕获并处理程序中的所有错误。

答案 3 :(得分:-1)

如果您可以使用safe-exceptions

您可以import Control.Exception.Safe并使用tryAnyDeep

tryDeep :: (C.MonadCatch m, MonadIO m, E.Exception e, NFData a) => m a -> m (Either e a)
tryDeep f = catch (liftM Right (evaluateDeep f)) (return . Left)
tryAnyDeep :: (C.MonadCatch m, MonadIO m, NFData a) => m a -> m (Either SomeException a)
tryAnyDeep = tryDeep

但是它解决方案期望有一个deriving (NFData)