deriving-trans 0.2.1.0 → 0.2.2.0
raw patch · 4 files changed
+258/−31 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Control.Monad.Trans.Compose: instance (GHC.Base.Monad (t2 m), GHC.Base.Monoid w) => Control.Monad.Writer.Class.MonadWriter w (Control.Monad.Trans.Compose.ComposeT (Control.Monad.Trans.Writer.Lazy.WriterT w) t2 m)
+ Control.Monad.Trans.Compose: instance GHC.Base.Monad (t2 m) => Control.Monad.Error.Class.MonadError e (Control.Monad.Trans.Compose.ComposeT (Control.Monad.Trans.Except.ExceptT e) t2 m)
+ Control.Monad.Trans.Compose: instance GHC.Base.Monad (t2 m) => Control.Monad.Reader.Class.MonadReader r (Control.Monad.Trans.Compose.ComposeT (Control.Monad.Trans.Reader.ReaderT r) t2 m)
+ Control.Monad.Trans.Compose: instance GHC.Base.Monad (t2 m) => Control.Monad.State.Class.MonadState s (Control.Monad.Trans.Compose.ComposeT (Control.Monad.Trans.State.Lazy.StateT s) t2 m)
Files
- CHANGELOG.md +6/−0
- deriving-trans.cabal +5/−3
- src/Control/Monad/Trans/Compose.hs +186/−14
- src/Control/Monad/Trans/Elevator.hs +61/−14
CHANGELOG.md view
@@ -1,7 +1,13 @@ # Revision history for deriving-trans +## 0.2.2.0 *30 Jan 2022*++* Add "base-case" instances for mtl's type classes to `ComposeT`.+* Add Haddock examples and improve comments.+ ## 0.2.1.0 *27 Jan 2022* +* Add `MonadTrans` and `MonadTransControl` instances to `Elevator`. * Use StandaloneKindSignatures. * Add Haddock documentation.
deriving-trans.cabal view
@@ -1,9 +1,10 @@ cabal-version: 3.0 name: deriving-trans-version: 0.2.1.0+version: 0.2.2.0 synopsis: Derive instances for monad transformer stacks description: Implementing instances for monad transformer stacks can be tedious.- <https://hackage.haskell.org/package/mtl mtl> defines each instance for each transfomer.+ <https://hackage.haskell.org/package/mtl mtl> defines each instance for each+ transfomer, but this can be avoided. 'Elevator' let's you access instances of the underlying monad of a transformer. Composing transformers with 'ComposeT' brings instances into scope during the@@ -11,7 +12,8 @@ 'Elevator' can be used with DerivingVia to select a specific transformer to derive instances for a transformer stack.- 'ComposeT' composes transformers and accumulates instances in a transformer stack.+ 'ComposeT' composes transformers and accumulates instances in a transformer+ stack. license: BSD-3-Clause license-file: LICENSE author: Felix Springer
src/Control/Monad/Trans/Compose.hs view
@@ -3,20 +3,33 @@ module Control.Monad.Trans.Compose where import Control.Monad.Base-import Control.Monad.Error.Class-import Control.Monad.IO.Class-import Control.Monad.Reader.Class-import Control.Monad.State.Class-import Control.Monad.Trans+import Control.Monad.Except+import Control.Monad.Reader+import Control.Monad.State import Control.Monad.Trans.Control import Control.Monad.Trans.Elevator-import Control.Monad.Writer.Class+import Control.Monad.Writer import Data.Kind +-- * 'ComposeT'+--+-- $composet+--+-- 'ComposeT' can be used in monad transformer stacks to derive instances in a clean way.+--+-- This also allows the usage of these instances, while in the middle of the transformer stack.+-- This proves particularly useful, when writing a runner for a transformer stack.+ -- | A newtype wrapper for two stacked monad transformers. -- -- Access instances of the intermediate monad @('t2' 'm')@, whenever 't1' implements -- 'MonadTrans'/'MonadTransControl'.+--+-- ==== Type level arguments+-- [@'t1' :: ('Type' -> 'Type') -> 'Type' -> 'Type'@] outer monad transformer+-- [@'t2' :: ('Type' -> 'Type') -> 'Type' -> 'Type'@] inner monad transformer+-- [@'m' :: 'Type' -> 'Type'@] monad+-- [@'a' :: 'Type'@] value type ComposeT :: ((Type -> Type) -> Type -> Type) -- ^ 't1' -> ((Type -> Type) -> Type -> Type) -- ^ 't2' -> (Type -> Type) -- ^ 'm'@@ -57,38 +70,74 @@ , MonadBaseControl b m ) => MonadBaseControl b (ComposeT t1 t2 m) --- | Elevated to @('t2' 'm')@.+-- | /OVERLAPPABLE/.+-- Elevated to @('t2' 'm')@. deriving via Elevator t1 (t2 (m :: * -> *))- instance+ instance {-# OVERLAPPABLE #-} ( Monad (t1 (t2 m)) , MonadTransControl t1 , MonadError e (t2 m) ) => MonadError e (ComposeT t1 t2 m) --- | Elevated to @('t2' 'm')@.-deriving via Elevator t1 (t2 (m :: * -> *))+-- | Set by 'ExceptT'.+deriving via ExceptT e (t2 (m :: * -> *)) instance+ ( Monad (t2 m)+ ) => MonadError e ((ComposeT (ExceptT e) t2) m)++-- | /OVERLAPPABLE/.+-- Elevated to @('t2' 'm')@.+deriving via Elevator t1 (t2 (m :: * -> *))+ instance {-# OVERLAPPABLE #-} ( Monad (t1 (t2 m)) , MonadTransControl t1 , MonadReader r (t2 m) ) => MonadReader r (ComposeT t1 t2 m) --- | Elevated to @('t2' 'm')@.-deriving via Elevator t1 (t2 (m :: * -> *))+-- | Set by 'ReaderT'.+deriving via ReaderT r (t2 (m :: * -> *)) instance+ ( Monad (t2 m)+ ) => MonadReader r ((ComposeT (ReaderT r) t2) m)++-- | /OVERLAPPABLE/.+-- Elevated to @('t2' 'm')@.+deriving via Elevator t1 (t2 (m :: * -> *))+ instance {-# OVERLAPPABLE #-} ( Monad (t1 (t2 m)) , MonadTrans t1 , MonadState s (t2 m) ) => MonadState s (ComposeT t1 t2 m) --- | Elevated to @('t2' 'm')@.-deriving via Elevator t1 (t2 (m :: * -> *))+-- | Set by 'StateT'.+deriving via StateT s (t2 (m :: * -> *)) instance+ ( Monad (t2 m)+ ) => MonadState s ((ComposeT (StateT s) t2) m)++-- | /OVERLAPPABLE/.+-- Elevated to @('t2' 'm')@.+deriving via Elevator t1 (t2 (m :: * -> *))+ instance {-# OVERLAPPABLE #-} ( Monad (t1 (t2 m)) , MonadTransControl t1 , MonadWriter w (t2 m) ) => MonadWriter w (ComposeT t1 t2 m) +-- | Set by 'WriterT'.+deriving via WriterT w (t2 (m :: * -> *))+ instance+ ( Monad (t2 m)+ , Monoid w+ ) => MonadWriter w ((ComposeT (WriterT w) t2) m)+++-- ** Run 'ComposeT'+--+-- $runComposet+--+-- You have to run the composed monad transformers to get back into the base monad at some point.+ -- | Run a transformer stack. -- -- This function takes the two individual monad transformer runners as arguments.@@ -109,3 +158,126 @@ -> (t2 m a -> m a) -- ^ run 't2' -> (ComposeT t1 t2 m a -> m a) runComposeT' runT1 runT2 = runT2 . runT1 . deComposeT++-- * Examples++-- ** Example 1: Create a new type class+--+-- $example1+--+-- When creating a new type class that supports 'ComposeT', you want to add recursive instances for+-- `ComposeT`.+--+-- @+-- class Monad m => MonadCustom m where+-- simpleMethod :: a -> m a+-- complicatedMethod :: (a -> m a) -> m a+-- @+--+-- You can easily derive those instances, after implementing an instance for 'Elevator'.+--+-- Then it's possible to derive the recursive instance.+-- This is an /OVERLAPPABLE/ instance, because we want to be able to add new instances through+-- transformers in a stack.+--+-- @+-- deriving via Elevator t1 (t2 (m :: * -> *))+-- instance {-# OVERLAPPABLE #-}+-- ( Monad (t1 (t2 m))+-- , MonadTransControl t1+-- , MonadCustom (t2 m)+-- ) => MonadCustom (ComposeT t1 t2 m)+-- @++-- ** Example 2: Add an instance+--+-- $example2+--+-- Add a type class instance for a new monad transformer, when there already is a recursive instance for 'ComposeT'.+--+-- @+-- newtype CustomT m a = CustomT { unCustomT :: IdentityT m a }+-- deriving newtype (Functor, Applicative, Monad)+-- deriving newtype (MonadTrans, MonadTransControl)+-- @+--+-- First we need the regular instance.+-- The method implementations are 'undefined' here, because they are not related to 'ComposeT'.+--+-- @+-- instance Monad m => MonadCustom (CustomT m) where+-- simpleMethod = undefined+-- complicatedMethod = undefined+-- @+--+-- To add an instance that takes priority over the recursive instance /FlexibleInstances/ are required.+--+-- @+-- deriving via CustomT (t2 (m :: * -> *))+-- instance+-- ( Monad (t2 m)+-- ) => MonadCustom ((ComposeT CustomT t2) m)+-- @++-- ** Example 3: Build a transformer stack+--+-- $example3+--+-- Create a monad transformer stack and wrap it using a newtype.+--+-- @+-- type (|.) = ComposeT+-- type Stack = StateT Int |. ReaderT Char |. CustomT |. ReaderT Bool |. IdentityT+-- newtype StackT m a = StackT { unStackT :: Stack m a }+-- deriving newtype (Functor, Applicative, Monad)+-- @+--+-- We are adding 'IdentityT' to the stack, so that all the other transformer instances end up in the stack.+-- Now we can simply derive just the instances, that we want.+--+-- @+-- deriving newtype (MonadState Int)+-- deriving newtype MonadCustom+-- @+--+-- We can even use 'Elevator' to access instances, that have been shadowed in the stack.+--+-- @+-- deriving (MonadReader Bool) via+-- ( (StateT Int)+-- ( Elevator (ReaderT Char)+-- ( CustomT+-- ( ReaderT Bool+-- ( IdentityT m)))))+-- @++-- ** Example 4: Run a transformer stack+--+-- $example4+--+-- This is the part, that actually contains your application logic.+-- Because of the setup with `ComposeT`, we won't have to worry about 'lift'ing during the+-- initialization.+--+-- @+-- runStackT :: MonadBaseControl IO m+-- => StackT m a+-- -> m (StT StackT a)+-- runStackT stackTma = do+-- let+-- runReaderT' :: MonadReader Bool m => ReaderT Char m a -> m a+-- runReaderT' tma = do+-- bool <- ask+-- let char = if bool+-- then \'Y\'+-- else \'N\'+-- runReaderT tma char+--+-- runStateT' :: MonadReader Char m => StateT Int m a -> m (a, Int)+-- runStateT' tma = do+-- char <- ask+-- let num = fromEnum char+-- runStateT tma num+--+-- runStateT' |. runReaderT' |. runCustomT |. (\\ tma -> runReaderT tma True) |. runIdentityT $ unStackT stackTma+-- @
src/Control/Monad/Trans/Elevator.hs view
@@ -11,9 +11,23 @@ import Control.Monad.Writer.Class import Data.Kind +-- * 'Elevator'+--+-- $elevator+--+-- 'Elevator' can be used to lift instances through monad transformers as long as they implement+-- 'MonadTrans' and 'MonadTransControl' instances.+--+-- 'MonadTransControl' is only necessary when there is atleast one method with a monadic argument.+ -- | A newtype wrapper for monad transformers. -- -- Access instances of the inner monad 'm'.+--+-- ==== Type level arguments+-- [@'t' :: ('Type' -> 'Type') -> 'Type' -> 'Type'@] monad transformer+-- [@'m' :: 'Type' -> 'Type'@] monad+-- [@'a' :: 'Type'@] value type Elevator :: ((Type -> Type) -> Type -> Type) -- ^ 't' -> (Type -> Type) -- ^ 'm' -> Type -- ^ 'a'@@ -54,34 +68,36 @@ pass tma = lift . pass . pure =<< tma -- * Examples------ $examples++-- ** Example 1: Recover submerged instances ----- == Example 1: Recover submerged instances+-- $example1 -- -- Let's assume you want to define a monad transformer stack. -- -- @ -- newtype StackT m a = StackT { unStackT :: ReaderT Char (ReaderT Bool m) a }--- deriving newtype (Functor, Applicative Monad)+-- deriving newtype (Functor, Applicative, Monad) -- @ ----- Now you want to expose the inner @(MonadReader Bool)@ instance with @(StackT m)@.+-- Now you want to expose the inner @('MonadReader' 'Bool')@ instance with @(StackT m)@. ----- Normally it's shadowed by the @(MonadReader Char)@ instance, but we can use @Elevator@ to access+-- Normally it's shadowed by the @('MonadReader' 'Char')@ instance, but we can use 'Elevator' to access -- the inner transformer. -- -- @--- deriving (MonadReader Bool) via (Elevator (ReaderT Char) (ReaderT Bool m))+-- deriving (MonadReader Bool) via Elevator (ReaderT Char) (ReaderT Bool m) -- @++-- ** Example 2: Custom transformer without boilerplate ----- == Example 2: Custom transformer without boilerplate+-- $example2 -- -- Let's assume you have defined a monad transformer. -- -- @ -- newtype CustomT m a = CustomT { unComposeT :: IdentityT m a }--- deriving newtype (Functor, Applicative Monad)+-- deriving newtype (Functor, Applicative, Monad) -- deriving newtype (MonadTrans, MonadTransControl) -- -- runCustomT :: CustomT m a -> m a@@ -91,16 +107,47 @@ -- Now you want to use this monad transformer in a transformer stack. -- -- @--- newtype StackT m a = { unStackT :: CustomT (ReaderT Bool m) a }+-- newtype StackT m a = StackT { unStackT :: CustomT (ReaderT Bool m) a } -- deriving newtype (Functor, Applicative Monad)--- deriving newtype (MonadTrans, MonadTransControl) -- @ ----- Unfortunately we can't derive a @Monad m => MonadReader Bool (StackT m)@ instance with--- GeneralizedNewtypeDeriving, without also adding the instance to @CustomT@.+-- Unfortunately we can't derive a @('Monad' m => 'MonadReader' 'Bool' (StackT m))@ instance with+-- /GeneralizedNewtypeDeriving/, without also adding the instance to @CustomT@. ----- To still derive this trivial instance we can use @Elevator@ with DerivingVia.+-- To still derive this trivial instance we can use 'Elevator' with /DerivingVia/. -- -- @ -- deriving (MonadReader Bool) via (Elevator CustomT (ReaderT Bool m)) -- @++-- ** Example 3: Adding an instance for 'Elevator'+--+-- $example3+--+-- Suppose you define a new type class.+--+-- @+-- class Monad m => MonadCustom m where+-- simpleMethod :: a -> m a+-- complicatedMethod :: (a -> m b) -> m b+-- @+--+-- A simple way to allow a type class to be lifted through other monad transformers is by adding an+-- instance for 'Elevator'.+--+-- You have to be careful about monadic state 'StT', when defining such instances using+-- 'MonadTransControl'.+--+-- @+-- instance (MonadCustom m, MonadTrans t) => MonadCustom (Elevator t m) where+-- simpleMethod = lift . simpleMethod+-- complicatedMethod f = (restoreT . pure =\<\<) $ liftWith $ \\ runT ->+-- complicatedMethod $ runT . f+-- @+--+-- Some useful examples (or exercises) are the instances for [mtl](https://hackage.haskell.org/package/mtl)'s type classes:+--+-- * 'MonadError'+-- * 'MonadReader'+-- * 'MonadState'+-- * 'MonadWriter'