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 转换器堆栈。

有关信息,在我的真实代码中,a实际上bhttp来自Network.HTTP.Conduit.

感谢您的见解。

有问题的最少代码

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

4 回答 4

2

http不抛出IOException,它抛出HttpException并且InternalIOException是后者的构造函数之一。

您应该捕获HttpExceptionSomeException以防万一您想捕获所有异常。

于 2015-03-07T15:04:58.417 回答
1

你需要的类型,

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

是您当前拥有的类型的特例

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

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

于 2014-03-21T08:19:47.923 回答
1

如果我正确理解您的问题,则使用提升基地没有问题。虽然aand的类型b只使用了约束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
于 2014-03-22T07:55:31.573 回答
0

如果您将类型签名更改为catchMonadCatch异常中包含,那么这将是微不足道的:

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 -> …

请注意,这不需要anor进行任何更改b

此外,duplode 和 Petr Pudlák 都指出,您可以自由地使 monadcatch变得尽可能具体,因为这样做不需要aor的任何合作b。所以这些解决方案中的任何一个都可以工作。

于 2015-03-18T22:39:57.007 回答