在MonadResource实例中捕获IO异常

3

简短版本

这里相同的问题,但是在通用的MonadResource实例中而不是显式的ResourceT m

详细版本

如何定义一个catch函数,使得:

import Control.Exception            (Exception, IOException)
import Control.Monad.Trans.Resource (MonadResource, runResourceT)

catch :: (MonadResource m, Exception e) -> m () -> (e -> m ()) -> m ()
catch = undefined

-- 'a' and 'b' are functions from an external library,
-- so I can't actually change their implementation
a, b :: MonadResource m => m ()
a = -- Something that might throw IO exceptions
b = -- Something that might throw IO exceptions

main :: IO ()
main = runResourceT $ do
    a `catch` \(e :: IOException) -> -- Exception handling
    b `catch` \(e :: IOException) -> -- Exception handling

我遇到的问题是:

看起来处理IO异常的唯一方法是退出ResourceT层,这让我困扰:我希望能够在本地处理异常,而不是通过monad transformers stack。

有关信息,在我的实际代码中,ab实际上是Network.HTTP.Conduithttp函数。

感谢您的见解。

具有问题的最小代码

使用http-conduit库可编译:ghc --make example.hs

{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}
import Control.Exception.Lifted     (IOException, catch)
import Control.Monad.Base           (liftBase)
import Control.Monad.Error          (MonadError(..), runErrorT)
import Control.Monad.Trans.Control  (MonadBaseControl)
import Control.Monad.Trans.Resource (MonadResource, runResourceT)

import Data.Conduit
import Data.Conduit.List            (consume)
import Data.Conduit.Text            (decode, utf8)
import Data.Text                    (Text)

import Network.HTTP.Client
import Network.HTTP.Conduit         (http)

main :: IO ()
main = do
    result <- runErrorT $ runResourceT f
    putStrLn $ "OK: " ++ show result

f :: (MonadBaseControl IO m, MonadResource m, MonadError String m) => m [Text]
f = do
    req      <- liftBase $ parseUrl "http://uri-that-does-not-exist.abc"
    manager  <- liftBase $ newManager defaultManagerSettings
    response <- (http req manager `catch` \(e :: IOException) -> throwError $ show e)
    response $$+- decode utf8 =$ consume

当执行该程序时,会出现以下错误输出:
InternalIOException getAddrInfo: does not exist (Name or service not known)

3
添加 MonadBaseControl IO m 的限制是否足够?MonadResource 的文档似乎暗示这是一个自然的做法。 - duplode
@duplode 我无法控制 ab 的实现,因为它们实际上是来自 http-conduit 库的函数。由于某种原因,resourcet 的维护者故意没有将 MonadBaseControl 添加为 MonadResource 的依赖项。 - koral
2
你捕获了错误的异常。你需要捕获HttpException或者SomeException - Tobias Brandt
确实,用 HttpException 替换 IOException 是可以的,谢谢。我建议您将其发布为答案,以便我可以验证它。 - koral
1
@TobiasBrandt:你可能错过了koral对你的回复,但由于你的评论给出了正确的答案,将其发布为正式答案会很好 :-) - sclv
@sclv:感谢提醒。 - Tobias Brandt
4个回答

2

http 不会抛出 IOException,而是会抛出 HttpException,其中 InternalIOException 是后者的构造函数之一。

如果您想捕获所有异常,应该捕获 HttpExceptionSomeException


1

你需要的类型,

a, b :: MonadResource m, MonadBaseControl IO m => m ()

是您当前拥有类型的一种特殊情况。
a, b :: MonadResource m => m ()

唯一的区别在于额外的类约束条件。您可以自由地使代码中的类型签名比默认情况下更不通用; 因此,只需更改ab的签名即可。


请查看我问题下面的注释:我无法控制 ab 的签名。 - koral
@koral,我不明白问题出在哪里。根据你的描述,在最坏的情况下,你应该能够用更少一般的签名(例如addInt :: Int -> Int -> Int; addInt = (+))将函数包装起来,即使这样也应该是不必要的,因为类型推断已经足够了。你能否添加一个更接近实际情况的最小可编译示例(例如使用相关的http-conduit函数)? - duplode
添加了最小可编译示例 :)。 - koral

1
如果我正确理解了您的问题,使用lifted-base没有任何问题。虽然ab的类型仅使用约束MonadResource m,但这并不意味着您不能在具有其他附加属性的monad上使用它们。例如,如果您在ResourceT内执行计算,则满足ab的约束,并且您还可以使用来自Control.Exception.Lifted的任何内容:
-- ...
import Control.Exception.Lifted

-- 'a' and 'b' are functions from an external library,
-- so I can't actually change their implementation
a, b :: MonadResource m => m ()
a = undefined -- Something that might throw IO exceptions
b = undefined -- Something that might throw IO exceptions

main :: IO ()
main = runResourceT $ do
    a `catch` \(e :: IOException) -> undefined -- Exception handling
    b `catch` \(e :: IOException) -> undefined -- Exception handling

我已经添加了一个最小化的示例来展示问题。 - koral

0
如果你修改catch的类型签名,使其包含MonadCatch来自exceptions包,那么这将是微不足道的:
import Control.Monad.Trans.Resource (MonadResource, runResourceT)
import Control.Monad.Catch          (catch)

a, b :: MonadResource m => m ()
a = …
b = …

main :: IO ()
main = runResourceT $ do
    a `catch` \e -> …
    b `catch` \e -> …

请注意,这不需要对 ab 进行任何更改。
此外,duplode 和 Petr Pudlák 都指出,您可以自由地使 catch 的单子成为尽可能具体,因为这样做不需要来自 ab 的合作。 因此,这些解决方案都可以使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接