In what situations should liftIO
be used? When I\'m using ErrorT String IO
, the lift
function works to lift IO actions into ErrorT>
Previous answers all explain the difference quite well. I just wanted to shed a some light on the inner workings so that it could be easier to understand how liftIO
isn't something magical (for the novice Haskellers like me).
liftIO :: IO a -> m a
is a wise tool just build upon
lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a
and most often used when the bottom monad is IO
. For the IO
monad it's definition is quite simple.
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
That simple... liftIO
is in fact just id
for the IO
monad and basically IO
is the only one that comes within the definition of the type class.
The thing is, when we have a monad type which is composed of several layers of monad transformers over IO
, we better have an MonadIO
instance for each one of those monad transformer layers. For example the MonadIO
instance of MaybeT m
requires m
to be of MonadIO
typeclass as well.
Writing a MonadIO
instance is basically a very simple task too. For MaybeT m
it is defined like
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
or for StateT s m
instance (MonadIO m) => MonadIO (StateT s m) where
liftIO = lift . liftIO
they are all the same. Imagine when you have a 4 layer transformer stack then you either need to do lift . lift . lift . lift $ myIOAction
or just liftIO myIOAction
. If you think about it, every lift . liftIO
will take you one layer down in the stack up until it digs all the way down to IO
at where liftIO
is defined as id
and finalizes with the same code like composed lift
s above.
So this is basically why regardless of the transformer stack configuration, provided that all underlaying layers are members of MonadIO
and MonadTrans
a single liftIO
is just fine.