简短版本
与这里相同的问题,但是在通用的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
我遇到的问题是:
- 在
Control.Exception
中,catch
只能处理裸的IO
; - 在
Control.Exception.Lifted
中,catch
需要一个MonadBaseControl
实例,而MonadResource
不幸地不是(我想知道为什么); MonadResource
意味着MonadThrow
,它定义了一个没有'catch'等效项的monadThrow
函数(我想知道为什么);
看起来处理IO
异常的唯一方法是退出ResourceT
层,这让我困扰:我希望能够在本地处理异常,而不是通过monad transformers stack。
有关信息,在我的实际代码中,a
和b
实际上是Network.HTTP.Conduit
的http
函数。
感谢您的见解。
具有问题的最小代码
使用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)
MonadBaseControl IO m
的限制是否足够?MonadResource
的文档似乎暗示这是一个自然的做法。 - duplodea
和b
的实现,因为它们实际上是来自http-conduit
库的函数。由于某种原因,resourcet
的维护者故意没有将MonadBaseControl
添加为MonadResource
的依赖项。 - koralHttpException
或者SomeException
。 - Tobias BrandtHttpException
替换IOException
是可以的,谢谢。我建议您将其发布为答案,以便我可以验证它。 - koral