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?
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
.