Tidying up Monads - turning application of a monad transformer into newtype monad

后端 未结 1 2132
栀梦
栀梦 2021-01-02 09:31

I am trying to take e.g. ExceptT a (StateT A M), for some concrete type A and monad M, and wrap them up into my new custom monads.

相关标签:
1条回答
  • 2021-01-02 09:56

    The normal pattern is to define a newtype for your complete transformer stack.

    data A = A
    data E = E
    
    newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a }
    

    If there are any pieces of the stack that add meaningful new capabilities on their own you'd also define newtypes for those pieces.

    Then you use GeneralizedNewtypeDeriving to get instances for all of the various monad classes.

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    
    -- base
    import Control.Applicative
    import Control.Monad
    
    -- transformers
    import Control.Monad.IO.Class
    import Control.Monad.Trans.Class
    import Control.Monad.Trans.Except
    import Control.Monad.Trans.State.Lazy
    
    -- mtl classes
    import Control.Monad.Cont.Class
    import Control.Monad.Error.Class
    import Control.Monad.Reader.Class
    import Control.Monad.State.Class
    import Control.Monad.Writer.Class
    
    data A = A
    data E = E
    
    newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a }
        deriving (Functor, Applicative, Monad, MonadIO,     -- classes from base and transformers
                  {- Alternative, MonadPlus, -}             -- if E is a monoid
                  MonadState A, MonadError E,               -- classes from mtl that MyBranchT provides
                  MonadCont, MonadReader r, MonadWriter w)  -- classes from mtl that might be available from m
    

    You'll have to write the MonadTrans instance by hand. lift is always just lift once for each of the transformers in the stack.

    instance MonadTrans MyBranchT where
        lift = MyBranchT . lift . lift
    

    If there are any pieces of the stack that add meaningful new capabilities X on their own you'd also define a new MonadX class for those capabilities, write MonadX instances for each of the monad transfomers (StateT, ExceptT, ContT, etc) if possible, and derive the MonadX instance for your transformer stack (MyBranchT).

    You'll also usually make a type synonym for MyBranchT Identity and functions to runMyBranchT and runMyBranch

    import Data.Functor.Identity
    
    type MyBranch a = MyBranchT Identity a
    
    runMyBranchT :: MyBranchT m a -> A -> m (Either E a, A)
    runMyBranchT mb s = runStateT (runExceptT (getMyBranchT mb)) s
    
    runMyBranch :: MyBranch a -> A -> (Either E a, A)
    runMyBranch mb s = runIdentity $ runMyBranchT mb s
    
    0 讨论(0)
提交回复
热议问题