Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ivan-m/monad-levels
Level-based interpretation of monad transformers
https://github.com/ivan-m/monad-levels
Last synced: about 1 month ago
JSON representation
Level-based interpretation of monad transformers
- Host: GitHub
- URL: https://github.com/ivan-m/monad-levels
- Owner: ivan-m
- License: mit
- Created: 2014-10-15T08:03:55.000Z (about 10 years ago)
- Default Branch: master
- Last Pushed: 2015-02-25T11:36:03.000Z (over 9 years ago)
- Last Synced: 2024-04-24T12:25:15.458Z (7 months ago)
- Language: Haskell
- Size: 410 KB
- Stars: 6
- Watchers: 5
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
`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.[mtl]: http://hackage.haskell.org/package/mtl
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):
```haskell
class (Monad m) => MonadBase m where
type BaseMonad m :: * -> *liftBase :: BaseMonad m a -> m a
class (MonadBase m) => MonadLevel m where
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 a -> LowerMonad m (InnerValue m a)) -- addInternal
-> 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.Advantages
----------* 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`).Disadvantages
-------------* Requires a _lot_ of GHC extensions.
* Requires usage of proxies when lifting/lowering typeclass methods.
* Large usage of associated types means type errors can be difficult
to decipher.* 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`).* Currently un-benchmarked; as such, it's not known how much of a
performance penalty this approach takes.* 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).