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
s李>IO
- 在中,
需要一个catch
实例,不幸的是MonadBaseControl
不是(我想知道为什么)李>MonadResource
- 意味着它定义了一个
函数,而没有它的“catch”等价物(我想知道为什么)李>monadThrow
- 意味着它定义了一个
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 -> …