Anatomy of a monad transformer
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:
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
.
上一篇: 当一个泛型不会是单子吗?
下一篇: Monad变压器的解剖