我试图从文件中读取一些值并捕获可能发生的每个异常(在"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
我能做些什么来捕捉每一个例外?
答案 0 :(得分:4)
return
不评估其参数,因此不会抛出任何异常。当您尝试打印结果时,评估发生在tryAny
之外。
使用evaluate
(可能与force
的{{1}}一起,具体取决于您的实际情况)。
答案 1 :(得分:2)
经过批次尝试evaluate
,force
和try
后,按照@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 serialized
是try.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建议的readMay
或readMaybe
重新编写整个代码,并强制严格读取文件。
答案 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)
。