Is there any difference between "MonadIO m" and "MonadBaseControl IO m"?

conduit, haskell, monads

Solution

`liftBase` is part of `MonadBase` which is a generalization of `MonadIO` for any base monad and, as you said, `MonadBase IO` provides the same functionality as `MonadIO`.

However, `MonadBaseControl` is a bit more complicated beast. In `MonadBaseControl IO m` you have

liftBaseWith :: ((forall a. m a -> IO (StM m a)) -> IO a) -> m a
restoreM     :: StM m a -> m a

It's easiest to see what the practical uses are by looking at examples. For example, the `bracket` from `base`has the signature

bracket ::  IO a -> (a -> IO b) -> (a -> IO c) -> IO c

With just `MonadBase IO m` (or `MonadIO m`) you can lift the main `bracket` invocation into `m` but the bracketing actions still need to be in plain old `IO`.

`throw` and `catch` are maybe even better examples:

throw :: Exception e => e -> a
catch :: Exception e => IO a -> (e -> IO a) -> IO a

You can easily thrown an exception from any `MonadIO m` and you can catch exception from `IO a` inside `MonadIO m` but again, both the action being run in `catch` and the exception handler itself need to be `IO a` not `m a`.

Now `MonadBaseControl IO` makes it possible to write `bracket` and `catch` in a way that allows the parameter actions to also be of type `m a` instead of being restricted to the base monad. The generic implementation for the above functions (as well as many others) can be found in the package `lifted-base`. For example:

catch   :: (MonadBaseControl IO m, Exception e) => m a -> (e -> m a) -> m a
bracket :: MonadBaseControl IO m => m a -> (a -> m b) -> (a -> m c) -> m c

EDIT: And now that I actually re-read your question properly...

No, I don't see any reason why the signature requires both `MonadIO m` and `MonadBaseControl IO m` since `MonadBaseControl IO m` should imply `MonadBase IO m` which enables the exact same functionality. So maybe it's just a left-over from some older version.

Looking at the source, it's probably just because `runTCPClient` calls `sourceSocket` and `sinkSocket` internally and those require `MonadIO`. I'm guessing that the reason why all the functions in the package don't simply use `MonadBase IO` is that `MonadIO` is more familiar to people and most monad transformers have a instance defined for `MonadIO m => MonadIO (SomeT m)` but users might have to write their own instance for `MonadBase IO`.

Problem

Function runTCPClient from network-conduit has the following signature: ``` runTCPClient :: (MonadIO m, MonadBaseControl IO m) => ClientSettings m -> Application m -> m () ``` `MonadIO m` provides ``` liftIO :: IO a -> m a ``` and `MonadBaseControl IO m` provides ``` liftBase :: IO a -> m a ``` There is no visible difference. Do they provide the same functionality? If yes, why the duplication in the type signature? If not, what's the difference?

Original source