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

1

10

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?

Panada answered 5/1, 2014 at 7:42 Comment(0)
A
12

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

Autocatalysis answered 5/1, 2014 at 9:12 Comment(1)
Thank you, now it makes sense. What confused me was that MonadBaseControl in conduit lacks any definition, probably only the name is imported/re-exported there.Panada

© 2022 - 2024 — McMap. All rights reserved.