我试图从文件中读取一些值并捕获可能发生的每个异常(在"请求宽恕比许可更容易"的心态中)。不过,我无法捕获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
一起使用,具体取决于您的实际情况)。
在对evaluate
、force
和try
进行了大量实验之后,按照 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
引起的异常,例如,如果ioA
是serialized <- 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 的建议使用 readMay
或 readMaybe
再次重写整个代码,并直接强制严格读取文件。
我遇到了同样的问题,没有找到一些评论中建议的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)
.