Reputation: 5073
Down in this document, we call liftIO in eval6 to perform I/O actions. Why do we need to lift in this case? Because there is no IO class for which we can instantiate a type as. Therefore, for I/O actions, we have to call lift to send the commands inwards. For eval6 , we would need to compose lift four times to print something. This is just inconvenient, so people create a new class MonadIO such that we only need to call liftIO once, without having to keep count of how many times to compose lift:
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
instance (Error e, MonadIO m) => MonadIO (ErrorT e m) where
liftIO = lift . liftIO
instance (MonadIO m) => MonadIO (ReaderT r m) where
liftIO = lift . liftIO
The citation comes from http://www.cs.virginia.edu/~wh5a/personal/Transformers.pdf
And again, I cannot understand why withoud liftIO we would have to call liftIO four times. I cannot see it therefore please make clear it ;)
Upvotes: 1
Views: 165
Reputation: 48611
class MonadTrans t where
lift :: Monad m => m a -> t m a
If you want
foo :: Monad m => m a -> ExceptT e (StateT s (ReaderT r m)) a
then you need to apply lift
once for each of the type constructors. First we get
lift :: Monad m
=> m a -> ReaderT r m a
then
lift . lift :: Monad m
=> m a -> StateT s (ReaderT r m) a
then
foo = lift . lift . lift :: Monad m
=> m a -> ExceptT e (StateT s (ReaderT r m)) a
Upvotes: 1