deriving-trans-0.2.2.0: src/Control/Monad/Trans/Elevator.hs
{-# LANGUAGE QuantifiedConstraints, UndecidableInstances, TupleSections #-}
module Control.Monad.Trans.Elevator where
import Control.Monad.Base
import Control.Monad.Error.Class
import Control.Monad.Reader.Class
import Control.Monad.State.Class
import Control.Monad.Trans
import Control.Monad.Trans.Control
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'
-> Type
newtype Elevator t m a = Ascend { descend :: t m a }
deriving newtype (Applicative, Functor, Monad)
deriving newtype (MonadTrans, MonadTransControl)
instance (Monad (t m), MonadTrans t, MonadBase b m) => MonadBase b (Elevator t m) where
liftBase = lift . liftBase
instance (Monad (t m), MonadTransControl t, MonadBaseControl b m) => MonadBaseControl b (Elevator t m) where
type StM (Elevator t m) a = StM m (StT t a)
liftBaseWith f = liftWith $ \ runT -> liftBaseWith $ \ runInBase -> f $ runInBase . runT
restoreM = restoreT . restoreM
instance (Monad (t m), MonadTrans t, MonadIO m) => MonadIO (Elevator t m) where
liftIO = lift . liftIO
instance (Monad (t m), MonadTransControl t, MonadError e m) => MonadError e (Elevator t m) where
throwError = lift . throwError
catchError throwing catching = (restoreT . pure =<<) $ liftWith $ \ runT ->
catchError (runT throwing) (runT . catching)
instance (Monad (t m), MonadTransControl t, MonadReader r m) => MonadReader r (Elevator t m) where
ask = lift ask
local f tma = (restoreT . pure =<<) $ liftWith $ \ runT ->
local f $ runT tma
instance (Monad (t m), MonadTrans t, MonadState s m) => MonadState s (Elevator t m) where
get = lift get
put = lift . put
instance (Monad (t m), MonadTransControl t, MonadWriter w m) => MonadWriter w (Elevator t m) where
tell = lift . tell
listen tma = liftWith (\ runT -> listen $ runT tma) >>= \ (sta, w) ->
(, w) <$> restoreT (pure sta)
pass tma = lift . pass . pure =<< tma
-- * Examples
-- ** 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)
-- @
--
-- 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
-- the inner transformer.
--
-- @
-- deriving (MonadReader Bool) via Elevator (ReaderT Char) (ReaderT Bool m)
-- @
-- ** 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 (MonadTrans, MonadTransControl)
--
-- runCustomT :: CustomT m a -> m a
-- runCustomT = runIdentityT . unComposeT
-- @
--
-- Now you want to use this monad transformer in a transformer stack.
--
-- @
-- newtype StackT m a = StackT { unStackT :: CustomT (ReaderT Bool m) a }
-- deriving newtype (Functor, Applicative Monad)
-- @
--
-- 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/.
--
-- @
-- 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'