Same question as in here, but within a generic MonadResource instance rather than an explicit ResourceT m.
How would you define a catch function such that:
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
The problems I run into are:
Control.Exception, catch only works on bare IOs ;Control.Exception.Lifted, catch requires an instance of MonadBaseControl, which MonadResource is unfortunately not (and I wonder why) ;MonadResource implies MonadThrow which defines a monadThrow function without its 'catch' equivalent (and I wonder why) ;It looks like the only way to handle IO exceptions is to exit the ResourceT layer, and this bothers me: I'd like to be able to handle exceptions locally without travelling through the monad transformers stack.
For information, in my real code, a and b are actually the http function from Network.HTTP.Conduit.
Thank you for your insights.
Compilable with ghc --make example.hs with http-conduit library installed:
{-# 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
When executed, this program ends in error with the following output:
InternalIOException getAddrInfo: does not exist (Name or service not known)
http does not throw IOException, it throws HttpException and InternalIOException is one of the latter's constructors.
You should either catch HttpException or SomeException in case you want to catch all exceptions.
The type you need,
a, b :: MonadResource m, MonadBaseControl IO m => m ()
Is a special case of the type you currently have
a, b :: MonadResource m => m ()
as the only difference is the extra class constraint. You are free to make the type signatures in your code less general than they would be by default; therefore, changing the signatures of a and b should be enough.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With