You can make your monad-agnostic functions using type classes instead of specific monad stacks.
Say you have this function, for example:
bangMe :: State String () bangMe = do str <- get put $ str ++ "!"
Of course, you understand that it works like a transformer, so you can write:
bangMe :: Monad m => StateT String m ()
However, if you have a function using a different stack, say ReaderT [String] (StateT String IO) () or something else, you will have to use the awful lift function! So how to avoid this?
The trick is to make the function signature even more general, so that it says that the State monad can appear anywhere in the monad stack. This is done as follows:
bangMe :: MonadState String m => m ()
This makes m be a monad that maintains a state of (practically) anywhere in the monad stack, and the function will thus work without lifting for any such stack.
There is one problem; since IO not part of mtl , it does not have a transformer (e.g. IOT ), and not a default class class. So what should you do when you want to raise IO actions arbitrarily?
MonadIO comes to the MonadIO ! It behaves almost the same with MonadState , MonadReader , etc., with the only difference being that it has a slightly different lifting mechanism. It works as follows: you can perform any IO action and use liftIO to turn it into an anastatic version of the monad. So:
action :: IO () liftIO action :: MonadIO m => m ()
Transforming all the monadic actions that you want to use in this way, you can bind the monads as much as you want, without the tedious lifting.