Avoid lifting with Monad Transformers

I have a problem that fits very well using the MT stack (or even one MT) through IO. Everything is fine, except that using the elevator before each action is terribly annoying! I suspect there's really nothing to be done, but I thought I would ask anyway.

I know that I need to remove whole blocks, but what if the code is really mixed types? Would it be nice if the GHC throws out some sort of syntactic sugar (e.g. <-$ = <- lift )?

+45
haskell monads monad-transformers
Jan 29 '12 at 16:32
source share
2 answers

For all standard mtl monads, you don't need to lift at all. get , put , ask , tell - they all work in any monad with the correct transformer somewhere on the stack. The missing piece of IO , and even there, liftIO raises an arbitrary IO action by an arbitrary number of layers.

This is done using types for each proposed effect: for example, MonadState provides get and put . If you want to create your own newtype packaging around the transformer stack, you can do deriving (..., MonadState MyState, ...) with the GeneralizedNewtypeDeriving extension or collapse your own instance:

 instance MonadState MyState MyMonad where get = MyMonad get put s = MyMonad (put s) 

You can use this to selectively expose or hide the components of your combi transformer, identifying some instances rather than others.

(You can easily extend this approach to all new monadic effects that you define yourself by defining your own type class and providing template instances for standard transformers, but all new monads are rare, most of the time you will simply compose the standard set suggested by mtl .)

+51
Jan 29 '12 at 16:45
source share

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 ++ "!" -- or just modify (++"!") 

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.

+42
Jan 29 '12 at 16:48
source share



All Articles