multistate 0.1.1 → 0.1.2
raw patch · 4 files changed
+206/−23 lines, 4 files
Files
- multistate.cabal +35/−6
- src/Control/Monad/MultiReader.hs +70/−5
- src/Control/Monad/MultiState.hs +82/−10
- src/Data/HList/HList.hs +19/−2
multistate.cabal view
@@ -1,5 +1,5 @@ Name: multistate-Version: 0.1.1+Version: 0.1.2 Cabal-Version: >= 1.8 Build-Type: Simple license: BSD3@@ -15,17 +15,46 @@ Synopsis: like mtl's ReaderT/StateT, but more than one contained value/type. Description:+ .+ == Introduction+ . When using multiple ReaderT's or StateT's in the same monad stack, it becomes necessary to lift the operations in order to affect a specific transformer. Using heterogenous lists (type level functions), a GADT and a corresponding type class, this package provides transformers that remove that necessity: MultiReaderT/MultiStateT can contain a heterogenous list of values.+ . The type inferred for the getter/setter determines which value is read/written.- See the Example application for some very basic usage.+ .+ == Example+ .+ > -- an IO action wrapped by a MultiState+ > -- containing both a Char and a [Char].+ > examplePrint :: MultiStateT (Cons [Char] (Cons Char Null)) IO ()+ > examplePrint = do+ > c <- mGet -- inferred to be :: m Char+ > cs <- mGet -- inferred to be :: m [Char]+ > lift $ putStrLn (c:cs)+ .+ This computation can be executed in the following way:+ .+ > main = evalMultiStateT+ > $ withMultiState 'H'+ > $ withMultiState "ello, World!"+ > $ examplePrint+ .+ ( you can find this example as an executable in the package. )+ .+ == Known Deficits+ . This package currently lacks a complete set of "lifting instances", i.e.- instance definitions for classes such as mtl's MonadReader "over" the newly- introduced monad transformers. These "lifting instances" would be necessary+ 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 compatability with existing transformers. Ping me if you find anything specific missing. @@ -39,14 +68,14 @@ flag build-example description: Build the MultiState-example example program- default: False + default: False library { exposed-modules:+ Data.HList.HList Control.Monad.MultiState Control.Monad.MultiReader other-modules:- Data.HList.HList build-depends: base >=4 && <6, mtl >=2 && <3,
src/Control/Monad/MultiReader.hs view
@@ -4,17 +4,23 @@ {-# LANGUAGE OverlappingInstances #-} {-# LANGUAGE UndecidableInstances #-} +-- | The multi-valued version of mtl's Reader / ReaderT+-- / MonadReader module Control.Monad.MultiReader- ( MultiReaderT(..)+ ( -- * MultiReaderT+ MultiReaderT(..) , MultiReaderTNull , MultiReader+ -- * MonadMultiReader class , MonadMultiReader(..)+ -- * functions , mAskRaw , withMultiReader , withMultiReaders , evalMultiReaderT , evalMultiReaderTWithInitial , mapMultiReaderT+ -- * re-exports , Cons -- re-export that stuff to allow writing type signatures. , Null ) where@@ -46,20 +52,48 @@ +-- | A Reader transformer monad patameterized by:+-- +-- * x - The list of types constituting the environment / input (to be read),+-- * m - The inner monad.+-- +-- 'MultiReaderT' corresponds to mtl's 'ReaderT', but can contain+-- a heterogenous list of types.+-- +-- This heterogenous list is represented using Types.Data.List, i.e:+-- +-- * @'Null'@ - The empty list,+-- * @'Cons' a b@ - A list where @/a/@ is an arbitrary type+-- and @/b/@ is the rest list.+-- +-- For example,+-- +-- > MultiReaderT (Cons Int (Cons Bool Null)) :: (* -> *) -> (* -> *)+-- +-- is a Reader wrapper containing the types [Int,Bool]. newtype MultiReaderT x m a = MultiReaderT { runMultiReaderTRaw :: StateT (HList x) m a } +-- | A MultiReader transformer carrying an empty state. type MultiReaderTNull = MultiReaderT Null -type MultiReader x a = MultiReaderT x Identity a+-- | A reader monad parameterized by the list of types x of the environment+-- / input to carry.+--+-- Similar to @Reader r = ReaderT r Identity@+type MultiReader x = MultiReaderT x Identity class ContainsType a c where setHListElem :: a -> HList c -> HList c getHListElem :: HList c -> a +-- | All methods must be defined.+--+-- The idea is: Any monad stack is instance of @MonadMultiReader a@, iff+-- the stack contains a @MultiReaderT x@ with /a/ element of /x/. class (Monad m) => MonadMultiReader a m where- mAsk :: m a+ mAsk :: m a -- ^ Access to a specific type in the environment. {- it might make seem straightforward to define the following class that@@ -111,6 +145,10 @@ instance MonadTrans (MultiReaderT x) where lift = MultiReaderT . lift +-- | Adds an element to the environment, thereby transforming a MultiReaderT+-- carrying an environment with types /(x:xs)/ to a a MultiReaderT with /xs/.+--+-- Think "Execute this computation with this additional value as environment". withMultiReader :: Monad m => x -> MultiReaderT (Cons x xs) m a@@ -121,6 +159,14 @@ put s' return a +-- | Adds a heterogenous list of elements to the environment, thereby+-- transforming a MultiReaderT carrying an environment with values+-- over types /xs++ys/ to a MultiReaderT over /ys/.+--+-- Similar to recursively adding single values with 'withMultiReader'.+--+-- Note that /ys/ can be Null; in that case the return value can be+-- evaluated further using 'evalMultiReaderT'. withMultiReaders :: Monad m => HList xs -> MultiReaderT (Append xs ys) m a@@ -136,18 +182,37 @@ => MonadMultiReader a (t m) where mAsk = lift $ mAsk +-- | A raw extractor of the contained HList (i.e. the complete environment).+--+-- For a possible usecase, see 'withMultiReaders'. mAskRaw :: Monad m => MultiReaderT a m (HList a) mAskRaw = MultiReaderT get +-- | Evaluate a computation over an empty environment.+--+-- Because the environment is empty, it does not need to be provided.+--+-- If you want to evaluate a computation over any non-Null environment, either+-- use+-- +-- * 'evalMultiReaderTWithInitial'+-- * simplify the computation using 'withMultiReader' / 'withMultiReaders',+-- then use 'evalMultiReaderT' on the result. evalMultiReaderT :: Monad m => MultiReaderT Null m a -> m a evalMultiReaderT k = evalStateT (runMultiReaderTRaw k) TNull +-- | Evaluate a reader computation with the given environment. evalMultiReaderTWithInitial :: Monad m- => HList a- -> MultiReaderT a m b+ => HList a -- ^ The initial state+ -> MultiReaderT a m b -- ^ The computation to evaluate -> m b evalMultiReaderTWithInitial c k = evalStateT (runMultiReaderTRaw k) c +-- | Map both the return value and the environment of a computation+-- using the given function.+--+-- Note that there is a difference to mtl's ReaderT,+-- where it is /not/ possible to modify the environment. mapMultiReaderT :: (m (a, HList w) -> m' (a', HList w)) -> MultiReaderT w m a
src/Control/Monad/MultiState.hs view
@@ -4,17 +4,24 @@ {-# LANGUAGE OverlappingInstances #-} {-# LANGUAGE UndecidableInstances #-} +-- | The multi-valued version of mtl's State / StateT+-- / MonadState module Control.Monad.MultiState- ( MultiStateT(..)+ (+ -- * MultiStateT+ MultiStateT(..) , MultiStateTNull , MultiState+ -- * MonadMultiState class , MonadMultiState(..)+ -- * functions , mGetRaw , withMultiState , withMultiStates , evalMultiStateT , evalMultiStateTWithInitial , mapMultiStateT+ -- * re-exports , Cons -- re-export that stuff to allow writing type signatures. , Null ) where@@ -46,20 +53,49 @@ +-- | A State transformer monad patameterized by:+-- +-- * x - The list of types constituting the state,+-- * m - The inner monad.+-- +-- 'MultiStateT' corresponds to mtl's 'StateT', but can contain+-- a heterogenous list of types.+-- +-- This heterogenous list is represented using Types.Data.List, i.e:+-- +-- * @'Null'@ - The empty list,+-- * @'Cons' a b@ - A list where @/a/@ is an arbitrary type+-- and @/b/@ is the rest list.+-- +-- For example,+-- +-- > MultiStateT (Cons Int (Cons Bool Null)) :: (* -> *) -> (* -> *)+-- +-- is a State wrapper containing the types [Int,Bool]. newtype MultiStateT x m a = MultiStateT { runMultiStateTRaw :: StateT (HList x) m a } +-- | A MultiState transformer carrying an empty state. type MultiStateTNull = MultiStateT Null -type MultiState x a = MultiStateT x Identity a+-- | A state monad parameterized by the list of types x of the state to carry.+--+-- Similar to @State s = StateT s Identity@+type MultiState x = MultiStateT x Identity class ContainsType a c where setHListElem :: a -> HList c -> HList c getHListElem :: HList c -> a +-- | All methods must be defined.+--+-- The idea is: Any monad stack is instance of @MonadMultiState a@, iff+-- the stack contains a @MultiStateT x@ with /a/ element of /x/. class (Monad m) => MonadMultiState a m where+ -- | state set function for values of type @a@. mSet :: a -> m ()+ -- | state get function for values of type @a@. mGet :: m a instance ContainsType a (Cons a xs) where@@ -84,20 +120,36 @@ instance MonadTrans (MultiStateT x) where lift = MultiStateT . lift +-- | Adds an element to the state, thereby transforming a MultiStateT over+-- values with types /(x:xs)/ to a MultiStateT over /xs/.+--+-- Think "Execute this computation with this additional value as state". withMultiState :: Monad m- => x- -> MultiStateT (Cons x xs) m a- -> MultiStateT xs m a+ => x -- ^ The value to add+ -> MultiStateT (Cons x xs) m a -- ^ The computation using the+ -- enlarged state+ -> MultiStateT xs m a -- ^ An computation using the+ -- smaller state withMultiState x k = MultiStateT $ do s <- get (a, TCons _ s') <- lift $ runStateT (runMultiStateTRaw k) (TCons x s) put s' return a +-- | Adds a heterogenous list of elements to the state, thereby+-- transforming a MultiStateT over values with types /xs++ys/ to a MultiStateT+-- over /ys/.+--+-- Similar to recursively adding single values with 'withMultiState'.+--+-- Note that /ys/ can be Null; in that case the return value can be+-- evaluated further using 'evalMultiStateT'. withMultiStates :: Monad m- => HList xs- -> MultiStateT (Append xs ys) m a- -> MultiStateT ys m a+ => HList xs -- ^ The list of values to add+ -> MultiStateT (Append xs ys) m a -- ^ The computation using the+ -- enlarged state+ -> MultiStateT ys m a -- ^ A computation using the+ -- smaller state withMultiStates TNull = id withMultiStates (TCons x xs) = withMultiStates xs . withMultiState x @@ -111,18 +163,38 @@ mSet = lift . mSet mGet = lift $ mGet +-- | Evaluate an empty state computation.+--+-- Because the state is empty, no initial state must be provided.+--+-- Currently it is not directly possible to extract the final state of a+-- computation (similar to @execStateT@ and @runStateT@ for mtl's StateT),+-- but you can use 'mGetRaw' if you need such functionality.+--+-- If you want to evaluate a computation over any non-Null state, either+-- use+-- +-- * 'evalMultiStateTWithInitial'+-- * simplify the computation using 'withMultiState' / 'withMultiStates',+-- then use 'evalMultiStateT' on the result. evalMultiStateT :: Monad m => MultiStateT Null m a -> m a evalMultiStateT k = evalStateT (runMultiStateTRaw k) TNull +-- | Evaluate a state computation with the given initial state. evalMultiStateTWithInitial :: Monad m- => HList a- -> MultiStateT a m b+ => HList a -- ^ The initial state+ -> MultiStateT a m b -- ^ The computation to evaluate -> m b evalMultiStateTWithInitial c k = evalStateT (runMultiStateTRaw k) c +-- | A raw extractor of the contained HList (i.e. the complete state).+--+-- For a possible usecase, see 'withMultiStates'. mGetRaw :: Monad m => MultiStateT a m (HList a) mGetRaw = MultiStateT get +-- | Map both the return value and the state of a computation+-- using the given function. mapMultiStateT :: (m (a, HList w) -> m' (a', HList w)) -> MultiStateT w m a -> MultiStateT w m' a'
src/Data/HList/HList.hs view
@@ -1,8 +1,12 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE TypeFamilies #-} -module Data.HList.HList(- HList(..)+-- | A GADT HList implementation+--+-- Probably exists somewhere else already, but why add a dependency+-- for something so simple.+module Data.HList.HList+ ( HList(..) ) where @@ -10,6 +14,7 @@ import Prelude hiding (reverse) import Types.Data.List ( Cons, Null )+import Data.Monoid @@ -22,3 +27,15 @@ instance (Show a, Show (HList b)) => Show (HList (Cons a b)) where show (TCons x y) = "(" ++ show x ++ "," ++ show y ++ ")"++instance Monoid (HList Null) where+ mempty = TNull+ mappend _ _ = TNull++instance (Monoid x, Monoid (HList xs))+ => Monoid (HList (Cons x xs))+ where+ mempty = TCons mempty mempty+ mappend (TCons x1 xs1) (TCons x2 xs2) =+ TCons (x1 `mappend` x2)+ (xs1 `mappend` xs2)