{-# LANGUAGE CPP #-} {-# OPTIONS_HADDOCK not-home #-} module Control.Effect.Carrier.Internal.Compose where import Control.Applicative import Control.Monad import qualified Control.Monad.Fail as Fail import Control.Monad.Trans import Control.Monad.Trans.Identity import Control.Monad.Fix import Control.Effect.Internal import Control.Effect.Internal.Derive import Control.Effect.Internal.Utils import Control.Monad.Trans.Control import Unsafe.Coerce -- | Composition of monad/carrier transformers. newtype ComposeT t (u :: (* -> *) -> * -> *) m a = ComposeT { getComposeT :: t (u m) a } deriving ( Functor, Applicative, Monad , Alternative, MonadPlus , MonadFix, MonadFail, MonadIO , MonadThrow, MonadCatch, MonadMask , MonadBase b, MonadBaseControl b , Carrier ) instance ( MonadTrans t , MonadTrans u , forall m. Monad m => Monad (u m) ) => MonadTrans (ComposeT t u) where lift m = ComposeT (lift (lift m)) {-# INLINEABLE lift #-} instance ( MonadTransControl t , MonadTransControl u , forall m. Monad m => Monad (u m) ) => MonadTransControl (ComposeT t u) where type StT (ComposeT t u) a = StT u (StT t a) liftWith main = ComposeT $ liftWith $ \lowerT -> liftWith $ \lowerU -> main (lowerU . lowerT .# getComposeT) {-# INLINEABLE liftWith #-} restoreT m = ComposeT (restoreT (restoreT m)) {-# INLINEABLE restoreT #-} -- | Composition of a list of carrier transformers. -- -- This is useful when you have multiple interpretations whose -- carriers you'd like to treat as one larger object, such that -- 'lift' lifts past all those carriers. -- -- For example: -- -- @ -- data Counter m a where -- Probe :: Counter m Int -- -- type CounterC = 'CompositionC' -- '[ 'Control.Effect.ReinterpretSimpleC' Counter '['Control.Effect.State.State' Int] -- , 'Control.Effect.State.StateC' Int -- ] -- -- runCounter :: ('Control.Effect.Carrier' m, 'Control.Effect.Threaders' '['Control.Effect.State.StateThreads'] m p) -- => CounterC m a -- -> m a -- runCounter = -- 'Control.Effect.State.runState' 0 -- . 'Control.Effect.reinterpretSimple' (\case -- Probe -> 'Control.Effect.State.state'' (\s -> (s+1,s)) -- ) -- . 'runComposition' -- @ -- -- Then you have @'lift' :: Monad m => m a -> CounterC m a@ newtype CompositionC ts m a = CompositionC { unCompositionC :: CompositionBaseT ts m a } #define DERIVE_COMP_M(ctx) \ deriving newtype instance ctx (CompositionBaseT ts m) \ => ctx (CompositionC ts m) #define DERIVE_COMP_T(ctx) \ deriving newtype instance ctx (CompositionBaseT ts) \ => ctx (CompositionC ts) DERIVE_COMP_M(Functor) DERIVE_COMP_M(Applicative) DERIVE_COMP_M(Monad) DERIVE_COMP_M(Alternative) DERIVE_COMP_M(MonadPlus) DERIVE_COMP_M(MonadFix) DERIVE_COMP_M(Fail.MonadFail) DERIVE_COMP_M(MonadIO) DERIVE_COMP_M(MonadThrow) DERIVE_COMP_M(MonadCatch) DERIVE_COMP_M(MonadMask) -- Yes, this is necessary. Don't ask, I haven't got a clue. deriving newtype instance (Monad b, MonadBase b (CompositionBaseT ts m)) => MonadBase b (CompositionC ts m) DERIVE_COMP_M(MonadBaseControl b) DERIVE_COMP_M(Carrier) DERIVE_COMP_T(MonadTrans) DERIVE_COMP_T(MonadTransControl) type family CompositionBaseT' acc ts :: (* -> *) -> * -> * where CompositionBaseT' acc '[] = acc CompositionBaseT' acc (t ': ts) = CompositionBaseT' (ComposeT acc t) ts type CompositionBaseT ts = CompositionBaseT' IdentityT ts type family CompositionBaseM (ts :: [(* -> *) -> * -> *]) (m :: * -> *) where CompositionBaseM '[] m = m CompositionBaseM (t ': ts) m = t (CompositionBaseM ts m) -- | Transform @'CompositionC' [t1, t2, ..., tn] m a@ to @t1 (t2 (... (tn m) ...)) a@ runComposition :: CompositionC ts m a -> CompositionBaseM ts m a -- This is a safe use of 'unsafeCoerce'; the two types are always representationally equal, -- without even needing the transformers in ts to be representational. -- GHC can only prove that, however, if ts is concrete. We could stick a 'Coercible' constraint, -- but in order to prove that constraint, both ComposeT and IdentityT needs to be in scope for the -- user. -- This seems like too much of a hassle, so unsafeCoerce is used instead. -- -- TODO(KingoftheHomeless): Investigate if the use of unsafeCoerce messes up optimizations. runComposition = unsafeCoerce {-# INLINE runComposition #-}