Exception 捕捉MonadResource实例中的IO异常 短版

Exception 捕捉MonadResource实例中的IO异常 短版,exception,haskell,conduit,Exception,Haskell,Conduit,与中的问题相同,但在泛型MonadResource实例中,而不是在显式ResourceT m中 长版本 您如何定义一个catch函数,以便: import Control.Exception (Exception, IOException) import Control.Monad.Trans.Resource (MonadResource, runResourceT) catch :: (MonadResource m, Exception e) -> m ()

与中的问题相同,但在泛型
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
我遇到的问题是:

  • 在中,
    catch
    仅适用于裸
    IO
    s
  • 在中,
    catch
    需要一个
    MonadBaseControl
    实例,不幸的是
    MonadResource
    不是(我想知道为什么)
  • 意味着它定义了一个
    monadThrow
    函数,而没有它的“catch”等价物(我想知道为什么)
处理
IO
异常的唯一方法似乎是退出
ResourceT
层,这让我很困扰:我希望能够在本地处理异常,而无需遍历monad transformers堆栈

请注意,在我的真实代码中,
a
b
实际上是来自的
http
函数

谢谢你的见解

问题的最小代码 可使用
ghc编译--例如.hs
安装了
http导管
库:

{-# 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
你需要的类型

a, b :: MonadResource m, MonadBaseControl IO m => m ()
是您当前拥有的类型的特例

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

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

如果我正确理解您的问题,那么使用提升底座就没有问题。尽管
a
b
的类型仅使用约束
MonadResource m
,但这并不意味着不能在具有其他附加属性的monad上使用它们。例如,如果您在
ResourceT
中执行计算,则它满足
a
b
的约束,并且您还可以使用
控制.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

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


您应该捕获
HttpException
SomeException
,以防捕获所有异常。

如果您更改
catch
的类型签名以包含在包中,那么它将是微不足道的:

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和Petr Pudlák都指出,您可以自由地使
catch
的单子尽可能具体,因为这样做不需要
a
b
的任何合作。因此,这些解决方案中的任何一个都会起作用。

添加
MonadBaseControl IO m
约束就足够了吗?
MonadResource
文档似乎暗示这是一件很自然的事情。@duplode我无法控制
a
b
的实现,因为它们实际上是
http管道
库中的函数。出于某种原因,
resourcet
的维护者故意没有将
MonadBaseControl
添加为
MonadResource
的依赖项。您捕获了错误的异常。您需要捕获
HttpException
SomeException
。实际上,用
HttpException
替换
IOException
是可行的,谢谢。我建议你把它作为一个答案贴出来,这样我就可以验证它。@TobiasBrandt:你可能错过了koral给你的回复,但既然你的评论有正确的答案,最好将其作为正确答案发布:-)Cf在我的问题下的评论:我无法控制
a
b
@koral的签名我无法了解问题所在。根据您所描述的,在最坏的情况下,您应该能够用较少的通用签名(如
addInt::Int->Int->Int;addInt=(+)
)将函数包装在包装器中,即使在给定类型推断的情况下,这也应该是不必要的。您是否可以添加一个更接近您实际拥有的最小可编译示例(例如,使用相关的
http管道
函数)?添加了最小可编译示例:)。我添加了一个显示问题的最小示例。
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 -> …