A monad transformer stack will provide access to many monad type classes. But you have to be careful, which transformers can be stacked.
deriving-trans uses 3 categories of monad transformers to implement instances for any monad transformer without knowing its exact definition, which solves mtl's “n2 problem”.
MonadTrans
-
lift
a monadic computationm a
into a monad transformert m a
. MonadTransControl
-
liftWith
runs a transformer’s computationt m a
in the base monadm a
, but you have to take care of the transformer’s monadic stateStT t a
explicitly withrestoreT
. MonadTransControlIdentity
-
liftWithIdentity
is just likeliftWith
, but without monadic state.
These 3 categories form a hierarchy where MonadTransControlIdentity
is stronger than MonadTransControl
, which is stronger than MonadTrans
.
Compatibility Matrix
The following table gives you two pieces of information. The “transformer category” tells you, which kind of transformer you can stack on top and still keep access to the type class. The “set by transformers” column tells you, which transformers implement the type class by themselves.
Note
|
This table is valid for deriving-trans 0.8.0.0. |
monad type class | transformer category | set by transformers | |
---|---|---|---|
|
|
set by base monad |
|
|
|
set by base monad |
|
|
|
set by base monad |
|
base |
|
|
|
|
|
|
|
|
|
||
|
|
set by base monad |
|
|
|
||
|
|
||
exceptions |
|
|
|
|
|
|
|
mtl |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
primitive |
|
|
set by base monad |
random |
|
|
|
|
|
||
|
|
||
resourcet |
|
|
|
unliftio |
|
|
set by base monad |
And now let me quickly explain how to make use of this table with an example.
MonadReader r
as an example.In the table you will find a row on MonadReader
, which will give you the following information.
A
MonadReader r m
instance can also implyMonadReader r (t m)
whent
satisfiesMonadTransControl
.
ReaderT r
orRWST r w s
can be used to implement an instance by themselves.
Here are some examples of transformer stacks for any Monad m
using (.|>)
from deriving-trans.
(TransparentT .|> ReaderT r .|> ExceptT e) m
-
-
✓ will have a
MonadReader r
instance, becauseExceptT e
satisfiesMonadTransControl
.
-
(TransparentT .|> ReaderT r .|> ContT r) m
-
-
❏ won’t have a
MonadReader r
instance, becauseContT r
doesn’t satisfyMonadTransControl
.
-
(TransparentT .|> ReaderT r1 .|> ReaderT r2) m
-
-
✓ will have a
MonadReader r2
instance. -
✓ will also have a
MonadReader r1
instance, unlessr1 ~ r2
.
-
(TransparentT .|> ExceptT e) m
-
-
will have a
MonadReader r
instance, wheneverm
satisfiesMonadReader r
.
-
Feel free to use this table as a cheat sheet or learning material. There are some intricacies though, which are hard to express in this format.
Tip
|
Some methods like |
Note
|
Some monad type classes are “set by base monad”.
I chose this for a few type classes, which only make sense when the instances come from the base monad Compare these instances to understand the difference.
|
Outlook
Currently I don’t have proofs for the compatibility matrix, so it’s possible, that some instances are not lawful and will change in the future.
I am working on supporting logict, but in this case I am not yet sure, whether we are allowed to lift it through any t
satisfying MonadTransControl
.