在MonadResource的实例中捕获IO异常

时间:2014-03-20 23:22:06

标签: exception haskell conduit

短版

here中的问题相同,但在通用的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变压器堆栈。

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

感谢您的见解。

带问题的最小代码

与安装了ghc --make example.hs库的http-conduit兼容:

{-# 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)

4 个答案:

答案 0 :(得分:2)

http不会抛出IOException,它会抛出HttpExceptionInternalIOException是后者的构造函数之一。

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

答案 1 :(得分:1)

您需要的类型,

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

是您目前所拥有类型的特例

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

唯一的区别是额外的类约束。您可以自由地使代码中的类型签名不像默认情况下那样通用;因此,更改ab的签名就足够了。

答案 2 :(得分:1)

如果我正确理解您的问题,使用提升基础没有问题。虽然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

答案 3 :(得分: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和PetrPudlák都指出你可以自由地使catch的monad成为你想要的,因为这样做不需要任何合作来自ab。所以这些解决方案都可行。