Haskell comes bundled with lots of monads. It can be difficult to get them to play together. For example, if you try this
you will get a type error because the first two statements require the do block to be working with a List monad whereas the third statement requires the do block to be working with an IO monad.
Haskell includes “transformer” versions of many monads which can be used to solve such problems. A transformer version of a monad m lets you create a version of m that can be used in conjunction with different monad. A transformer is not a monad, but a constructor of monads.
Yes, this ups the level of abstraction. Monads construct types. Transformers construct monads. But once you have accepted the notion of a “constructor of whatchamacallits”, the notion of a “constructor of whatchamacallit constructors” isn't much of a stretch.
To be more explicit a transformer mt of a monad n is a meta type with two type parameters m and a. The first parameter m must be a monad. When it is substituted, mt m becomes another monad. When both parameters are substituted, mt m a becomes a monadic object. The monadic object will act like one of n's objects with respect to (>>=) and return but can be used like the wrapper m in ways yet to be specified.
The transformer for the List monad has a meta type something like this
When m is given a specific value in List m a, you get a monad which acts like a List monad with respect to (>>=) and return but whose list is wrapped in the monad you gave to m.
The definition of ListT is actually written with an accessor for the monadic object of type m [a]. This is the way it really looks
(Here newtype is used because type merely names an alias for another type and we cannot specify accessor when naming an alias.)
Since the only ListT m I will be writing about is ListT IO, it is convenient to give it a name:
Since a Lio monadic object's (>>=) and return act like a List, its interior objects come from the list which the IO monad wraps. Put another way, if the IO's interior object is ['one','two'] then the Lio's interior objects are 'one' and 'two'.
Actually using the IO monad within Lio is not straight forward. This will not work
The problem is that putStrLn produces an IO () and this do block needs a Lio (). We solve that problem by “lifting” the putStrLn action into the containing Lio.