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



我试图从文件中读取一些值并捕获可能发生的每个异常(在"请求宽恕比许可更容易"的心态中)。不过,我无法捕获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

我该怎么做才能捕获每个异常?

return

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

为此使用evaluate(可能与Control.DeepSeq force一起使用,具体取决于您的实际情况)。

在对evaluateforcetry进行了大量实验之后,按照 Cheplyaka @Roman的建议,我想出了这个解决方案,现在我看到了最终结果,这似乎几乎非常明显:

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进行深度评估。从文档中:

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引起的异常,例如,如果ioAserialized <- readFile; return $ read serialized的结果,并且要从中读取的文件不存在。
  • 如果没有类型信息,则无法在代码中内联try.evaluateIoCompletely,因为 try 需要知道它应该捕获哪个异常。此信息现在通过返回类型tryAnyStrict提供,该返回类型将try :: Exception e => IO a -> IO (Either e a)的类型e解析为最一般的异常类型SomeException

现在,在我开始工作tryAnyStrict我仍然遇到惰性计算的问题,因为如果a <- ioA失败 - 其中 ioA 是对从文件中懒惰读取的字符串read的结果 - (例如文件内容没有read所需的格式)然后文件仍然没有完全读取,因此仍然打开。对文件的后续写入将失败("打开文件:资源繁忙")。所以。。。我实际上可能会按照 @kqr 和 @Roman Cheplyaka 的建议使用 readMayreadMaybe 再次重写整个代码,并直接强制严格读取文件。

我遇到了同样的问题,没有找到一些评论中建议的readMaybe。但是我发现了另一个有用的读取变体,它解决了我的问题 - readIO

readIO 函数与 read 类似,不同之处在于它向 IO monad 发出解析失败的信号,而不是终止程序。

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

如果可以使用安全异常

您可以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).

最新更新