[ control, library, mit ] [ Propose Tags ]

Versions [faq] 0.1.0.0, 0.1.0.1 CHANGELOG.md base (==4.7.*), constraints (==0.4.*), transformers (>=0.2 && <0.5), transformers-compat (>=0.1 && <1) [details] MIT Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com Control https://github.com/ivan-m/monad-levels https://github.com/ivan-m/monad-levels/issues head: git clone git://github.com/ivan-m/monad-levels.git by IvanMiljenovic at 2015-02-03T11:11:46Z NixOS:0.1.0.1 1307 total (8 in the last 30 days) (no votes yet) [estimated by Bayesian average] λ λ λ Docs available Last success reported on 2015-02-03

Maintainer's Corner

For package maintainers and hackage trustees

[back to package description]

monad-levels

Why not mtl?

The oft-spouted problem with the standard monad transformer library mtl and similar libraries is that instances are quadratic: you need a separate instance for each valid combination of transformer + typeclass.

For end users, this isn't really a problem: after all, all the required instances have already been written for you!

But what happens if you have a custom transformer, or a custom typeclass?

What about if you want to have something like MonadIO but for a different base monad?

Then you need to write all those extra instances.

What makes it more frustrating is that many of the instance definitions are identical: typically for every transformer (using StateT s m as an example) it becomes a matter of:

• Possibly unwrap the transformer from a monadic value to get the lower monad (e.g. StateT s m a -> m (a,s));

• Possibly add internal values (e.g. m a -> m (a,s));

• Wrap the lower monad from the result of the computation back up into the transformer (e.g. m (a,s) -> StateT s m a).

The solution

Ideally, instead we'd have something along the lines of (simplified):


type BaseMonad m :: * -> *

liftBase :: BaseMonad m a -> m a

type LowerMonad m :: * -> *

type InnerValue m a :: *

-- A continuation-based approach for how to lift/lower a monadic value.
wrap :: (    (m a -> LowerMonad m (InnerValue m a)             -- unwrap
-> LowerMonad m (InnerValue m a)
)
-> m a


With these two classes, we could then use Advanced Type Hackery (TM) to let us instead just specify instances for the transformers/monads that do have direct implementations for a typeclass, and then have the rest defined for us!

It turns out that this approach is even powerful enough to make liftBase redundant, and it isn't limited to just lifting a monad but can instead be used for arbitrary functions.

• Minimal specification required for adding new typeclasses: just specify the instances for monads that satisfy it, and then use the provided machinery to lift/lower methods to other transformers in the monadic stack.

• Works even for polyvariadic functions.

• Still allows specifying whether certain transformers do not allow some constraints to pass through (e.g. ContT does not allow access to a WriterT).

• Due to usage of closed type-families, it is not possible to add extra instances to typeclasses (i.e. it is not possible to use a custom State monad/monad-transformer with Control.Monad.Levels.State).
• Lowering polyvariadic functions requires specifying the type of the function using a specific grammar (though the common m a -> m a case is pre-defined).