I'm trying to learn monad transformers, based on the standard Haskell libraries (mtl? transformers? not sure which one came with my download of the Haskell platform - 7.4.1).
What I believe I've noticed is a common structure for each monad transformer definition:
base type ('Base')
transformer type ('BaseT')
Monad instance
MonadTrans instance
MonadIO instance
transformer class ('MonadBase')
some operations
instances for other 'BaseT's
So for example, for the Writer monad, there'd be:
Is this how monad transformers are organized? Am I missing anything/do I have any incorrect details?
The motivation for this question is figuring out:
Monad transformers allow developers to compose the effects of different monads, even if the monads themselves are not the same. An example is writing a do-statement that can: abort computation (ExceptT), thread state (StateT), and connect to a database (via a Haskell library such as persistence or esqueleto).
The Reader monad (also called the Environment monad). Represents a computation, which can read values from a shared environment, pass values from function to function, and execute sub-computations in a modified environment. Using Reader monad for such computations is often clearer and easier than using the State monad.
A Monad that can convert any given IO[A] into a F[A] , useful for defining parametric signatures and composing monad transformer stacks.
A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.
mtl
package doesn't implement monad transformers. At least WriterT is just reexported from transformers
.
transformers
package implements WriterT
, which is a monad transformer itself. Writer
is just an alias:
type Writer w = WriterT w Identity
Some libraries can implement Writer
separately, but anyway it is just a special case of WriterT
. (Identity
is a trivial monad, it doesn't have any additional behavior.)
MonadTrans
allows you to wrap underlying monad into the transformed one. You can live without it, but you will need to perform manual wrapping (see MonadTrans
instance definition for WriterT
for example how to do it). The only use case where you really need MonadTrans
-- when you don't know actual type of transformer.
MonadWriter
is a type class declared in mtl
. It's methods (writer
, pass
, tell
and listen
) are the same as function for WriterT
. It allows to wrap (automatically!) WriterT
computation through stack of transformers, even if you don't know exact types (and even number!) of transformers in the stack.
So, WriterT
is the only type which is "required".
For other monad transformers it is the same: BaseT
is a transformer, Base
is a monad without underlying monad and MonadBase
is a type class -- class of all monads, that have BaseT
somewhere in transformers stack.
ADDED:
You can find great explanation in RWH book
Here is a basic example:
import Control.Monad.Trans
import Control.Monad.Trans.Writer
import Control.Monad.Trans.Reader hiding (ask)
-- `ask` from transformers
-- ask :: Monad m => ReaderT r m r
import qualified Control.Monad.Trans.Reader as TransReader (ask)
-- `ask` from mtl
-- ask :: MonadReader r m => m r
import qualified Control.Monad.Reader as MtlReader (ask)
-- Our monad transformer stack:
-- It supports reading Int and writing String
type M m a = WriterT String (ReaderT Int m) a
-- Run our monad
runM :: Monad m => Int -> M m a -> m (a, String)
runM i action = runReaderT (runWriterT action) i
test :: Monad m => M m Int
test = do
tell "hello"
-- v <- TransReader.ask -- (I) will not compile
v1 <- lift TransReader.ask -- (II) ok
v2 <- MtlReader.ask -- (III) ok
return (v1 + v2)
main :: IO ()
main = runM 123 test >>= print
Note that (I)
will be rejected by compiler (try it to see the error message!). But (II)
compiles, thanks to MonadTrans
("explicit lifting"). Thanks to MonadReader
, (III)
works out of the box ("implicit lifting"). Please read RWH book for explanation how it works.
(In the example we import ask
from two different modules, that is why we need qualified import. Usually you will use only one of them at a time.)
Also I didn't mean to specifically ask about
Writer
.
Not sure I understand... Reader
, State
and others use the same schema. Replace Writer
with State
and you will have an explanation for State
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With