与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
我遇到的问题是:
Control.Exception
中,catch
仅适用于裸IO
s; Control.Exception.Lifted
中,catch
需要MonadBaseControl
的实例,MonadResource
不幸不(我想知道为什么); MonadResource
隐含MonadThrow
,它定义了monadThrow
函数,但没有' catch'相当于(我想知道为什么); 看起来处理IO
例外的唯一方法是退出ResourceT
图层,这让我感到困扰:我希望能够在本地处理异常,而无需通过monad变压器堆栈。
有关信息,在我的真实代码中,a
和b
实际上是来自Network.HTTP.Conduit
的http
函数。
感谢您的见解。
与安装了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)
答案 0 :(得分:2)
http
不会抛出IOException
,它会抛出HttpException
而InternalIOException
是后者的构造函数之一。
如果您想要捕获所有异常,您应该抓住HttpException
或SomeException
。
答案 1 :(得分:1)
您需要的类型,
a, b :: MonadResource m, MonadBaseControl IO m => m ()
是您目前所拥有类型的特例
a, b :: MonadResource m => m ()
唯一的区别是额外的类约束。您可以自由地使代码中的类型签名不像默认情况下那样通用;因此,更改a
和b
的签名就足够了。
答案 2 :(得分:1)
如果我正确理解您的问题,使用提升基础没有问题。虽然a
和b
的类型仅使用约束MonadResource m
,但这并不意味着您不能在具有其他附加属性的monad上使用它们。例如,如果您在ResourceT
内执行计算,则它会满足a
和b
的约束,您还可以使用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 -> …
请注意,不要求对a
或b
进行任何更改。
另外,duplode和PetrPudlák都指出你可以自由地使catch
的monad成为你想要的,因为这样做不需要任何合作来自a
或b
。所以这些解决方案都可行。