Great question!
A monad transformer is a type that adds some functions to an arbitrary base monad, while preserving monad. Unfortunately, monad transformers are inexpressible in C # because they make significant use of higher-grade types. So, while working at Haskell,
class MonadTrans (t :: (* -> *) -> (* -> *)) where lift :: Monad m => ma -> tma transform :: Monad m :- Monad (tm)
Follow this line by line. The first line declares that the monad transformer is a type t that takes an argument of the form * -> * (i.e., a Type that expects one argument) and turns it into another type of the form * -> * . When you understand that all monads are of the form * -> * , you can see that the intention is that t turns the monads into other monads.
The next line says that all monad transformers must support the lift operation, which takes an arbitrary monad m and lifts it into the world of the transformer tm .
Finally, the transform method says that for any monad m , tm should also be a monad. I use the entry operator :- from the constraints package .
This will make more sense with an example. It uses a monad transformer, which adds Maybe -ness to the arbitrary base monad m . The nothing operator allows you to cancel the calculation.
newtype MaybeT ma = MaybeT { runMaybeT :: m (Maybe a) } nothing :: Monad m => MaybeT ma nothing = MaybeT (return Nothing)
For MaybeT to be a monad transformer, it must be a monad when its argument is a monad.
instance Monad m => Monad (MaybeT m) where return = MaybeT . return . Just MaybeT m >>= f = MaybeT $ m >>= maybe (return Nothing) (runMaybeT . f)
Now write an implementation of MonadTrans . The lift implementation wraps the return value of the base monad in Just . The transform implementation is uninteresting; he simply tells the GHC constraint resolver to make sure MaybeT really a monad whenever there is an argument.
instance MonadTrans MaybeT where lift = MaybeT . fmap Just transform = Sub Dict
Now we can write a monadic calculation that MaybeT uses to add failure, for example, to the State monad. lift allows you to use the standard methods State get and put , but we also have access to nothing if we need to fail the calculation. (I was thinking about using your IEnumerable (aka [] ) example, but there is something perverse about adding rejection to the monad that already supports it.)
example :: MaybeT (State Int) () example = do x <- lift get if x < 0 then nothing else lift $ put (x - 1)
What makes monad transformers really useful is their stackability. This allows you to create large monads with many possibilities from many small monads with one possibility each. For example, a given application may need to perform I / O, read configuration variables, and throw exceptions; it will be encoded with type type
type Application = ExceptT AppError (ReaderT AppConfig IO)
There are tools in the mtl package that will help you abstract from the exact collection and ordering of monad transformers in a given stack, allowing you to send calls to lift .