Module documentation for 0.7.1.1
When using multiple Reader/Writer/State transformers in the same monad stack, it becomes necessary to lift the operations in order to affect a specific transformer. Using heterogeneous lists (and all kinds of GHC extensions magic), this package provides transformers that remove that necessity: MultiReaderT/MultiWriterT/MultiStateT can contain a heterogeneous list of values.
The type inferred for the getter/setter determines which value is read/written.
simpleExample :: IO () simpleExample = runMultiStateTNil_ -- start with an empty state, -- i.e. :: MultiStateT ' IO $ withMultiStateA 'H' -- "adding" a char to the state $ withMultiStateA "ello, World!" -- and a string $ do -- so: -- the monad here is MultiStateT '[String, Char] IO let combinedPrint = do -- no type signature necessary c <- mGet -- type of mGet inferred to be m Char cs <- mGet -- inferred to be m String lift $ putStrLn (c:cs) combinedPrint mSet 'J' -- we modify the Char in the state. -- again, the type is inferred, -- without any manual lifting. combinedPrint
The output is:
Hello, World! Jello, World!
( you can find both this and a more complex example in an executable in the package. )
If you try to execute an action that requires a specific type in the state, but the current state does not contain that type, the error message is something like
No instance for (Control.Monad.MultiState.ContainsType Foo ') x
Foo is the missing type.
Compatibility with Single-Valued Transformers
It is possible to run single-valued actions inside multi-valued
transformers using the
inflate functions. A function transforming
a multi-valued transformer with exactly one element into a
single-valued transformer would be trivial, but it is currently not provided.
(Will refer to StateT in this paragraph, but equally valid for Reader/Writer)
The mtl monad transformers make use of primarily three methods to “unwrap”
a transformed value:
execStateT. These three all have a type
matching the pattern
s -> t m a -> m b, they differ in what
We will use a different naming scheme, for three reasons:
“run”, “eval” and “exec” are not in any way intuitive, and should be suffixed in any case.
For MultiStateT, it makes sense to transform an existing transformer, adding another state. The signature would be close to that of runStateT, only without the unwrapping part, i.e.
s -> t m a -> t' m b, where
sis the initial state, and
t'with another state added.
Sometimes you might want to add/run a single state, or a bunch of them. For example, when running an arbitrary StateT, you would need to provide a HList of initial states, and would receive a HList of final states.
Our naming scheme will instead be:
runStateT.*unwraps a StateT. A suffix controls what exactly is returned by the function. There is a special version for when the list of states is Nil,
withStateT.*adds one or more states to a subcomputation. A suffix controls the exact return value.
withStates /-------------------------------------------------------\ | withState withState .. withState v StateT '[s, ..] m --------> StateT '[..] m --------> .. --------> StateT ' m | <-------- | | (withoutState) | | | | | | runStateT runStateTNil | \--------------------> m .. <---------------------------/
Specific functions are (constraints omitted):
runMultiStateT = runMultiStateTAS runMultiStateTA :: HList s -> MultiStateT s m a -> m a runMultiStateTAS :: HList s -> MultiStateT s m a -> m (a, s) runMultiStateTSA :: HList s -> MultiStateT s m a -> m (s, a) runMultiStateTS :: HList s -> MultiStateT s m a -> m s runMultiStateT_ :: HList s -> MultiStateT s m a -> m () runMultiStateTNil :: MultiStateT ' m a -> m a runMultiStateTNil_ :: MultiStateT ' m a -> m () withMultiState = withMultiStateAS withMultiStateA :: s -> MultiStateT (s ': ss) m a -> MultiStateT ss m a withMultiStateAS :: s -> MultiStateT (s ': ss) m a -> MultiStateT ss m (a, s) withMultiStateSA :: s -> MultiStateT (s ': ss) m a -> MultiStateT ss m (s, a) withMultiStateS :: s -> MultiStateT (s ': ss) m a -> MultiStateT ss m s withMultiState_ :: s -> MultiStateT (s ': ss) m a -> MultiStateT ss m () withMultiStates = withMultiStatesAS withMultiStatesAS :: HList s1 -> MultiStateT (Append s1 s2) m a -> MultiStateT s2 m (a, HList s1) withMultiStatesSA :: HList s1 -> MultiStateT (Append s1 s2) m a -> MultiStateT s2 m (HList s1, a) withMultiStatesA :: HList s1 -> MultiStateT (Append s1 s2) m a -> MultiStateT s2 m a withMultiStatesS :: HList s1 -> MultiStateT (Append s1 s2) m a -> MultiStateT s2 m (HList s1) withMultiStates_ :: HList s1 -> MultiStateT (Append s1 s2) m a -> MultiStateT s2 m () withoutMultiState :: MultiStateT ss m a -> MultiStateT (s ': ss) m a
This package currently lacks a complete set of “lifting instances”, i.e. instance definitions for classes such as mtl’s MonadWriter “over” the newly introduced monad transformers, as in
instance (MonadWriter w m) => MonadWriter w (MultiStateT c m) where ..
These “lifting instances” would be necessary to achieve full compatibility with existing transformers. Ping me if you find anything specific missing.
0.7.1.1 May 2016
- Adapt for ghc-8
0.7.1.0 March 2016
- Add new method
withoutMultiFoo, inverse of
0.7.0.0 February 2016
0.6.2.0 June 2015
- Add MonadFix instances
0.6.1.0 June 2015
- Export classes from transformer modules
0.6.0.0 June 2015
Add inflate functions (e.g.
StateT _ -> MultiStateT _)
Move changelog from
0.5.0.0 March 2015
Breaking changes (!):
Refactor some parts of the interface, see “naming scheme” in the README; The changes are:
Start using hspec; Add proper cabal test-suite.
0.4.0.0: March 2015
Put classes (
MonadMulti*) into separate modules
Add Strict and Lazy variants
Deprecate previous modules
0.3.0.0 January 2015
0.2.0.0 January 2015
Start using DataKinds and TypeOperators to make the HList representation more readable. The translation roughly is:
Null -> ' Cons a Null -> '[a] Cons a b -> a ': b TNull -> HNil TCons a b -> a :+: b
Remove dependency on
0.1.3.2 September 2014
Clean up / Add dependencies
0.1.2 September 2014
0.1.1 June 2014
- First version published on hackage