    {-# LANGUAGE ConstraintKinds         #-}
    {-# LANGUAGE CPP                     #-}
    {-# LANGUAGE FlexibleContexts        #-}
    {-# LANGUAGE FlexibleInstances       #-}
    {-# LANGUAGE FunctionalDependencies  #-}
    {-# LANGUAGE MultiParamTypeClasses   #-}
    {-# LANGUAGE RankNTypes              #-}
    {-# LANGUAGE ScopedTypeVariables     #-}
    {-# LANGUAGE TypeFamilies            #-}
    {-# LANGUAGE TypeOperators           #-}
    {-# LANGUAGE UndecidableInstances    #-}
    #if __GLASGOW_HASKELL__ >= 800
    {-# LANGUAGE UndecidableSuperClasses #-}
    #endif
    -- | See overview in the README.md
    module Control.Monad.Trans.Unlift
        ( -- * Trans
          MonadTransUnlift
        , Unlift (..)
        , askUnlift
        , askRun
          -- * Base
        , MonadBaseUnlift
        , UnliftBase (..)
        , askUnliftBase
        , askRunBase
          -- * Reexports
        , MonadTrans (..)
        , MonadBase (..)
        , MonadTransControl (..)
        , MonadBaseControl (..)
        ) where

    import           Control.Monad               (liftM)
    import           Control.Monad.Base          (MonadBase (..))
    import           Control.Monad.Trans.Class   (MonadTrans (..))
    import           Control.Monad.Trans.Control (MonadBaseControl (..),
                                                  MonadTransControl (..))
    import           Data.Constraint             ((:-), (\\))
    import           Data.Constraint.Forall      (Forall, inst)

    -- | A function which can move an action down the monad transformer stack, by
    -- providing any necessary environment to the action.
    --
    -- Note that, if ImpredicativeTypes worked reliably, this type wouldn't be
    -- necessary, and 'askUnlift' would simply include a more generalized type.
    --
    -- Since 0.1.0
    newtype Unlift t = Unlift { unlift :: forall a n. Monad n => t n a -> n a }

    class    (StT t a ~ a) => Identical t a
    instance (StT t a ~ a) => Identical t a

    -- | A monad transformer which can be unlifted, obeying the monad morphism laws.
    --
    -- Since 0.1.0
    class    (MonadTransControl t, Forall (Identical t)) => MonadTransUnlift t
    instance (MonadTransControl t, Forall (Identical t)) => MonadTransUnlift t

    mkUnlift :: forall t m a . (Forall (Identical t), Monad m)
             => (forall n b. Monad n => t n b -> n (StT t b)) -> t m a -> m a
    mkUnlift r act = r act \\ (inst :: Forall (Identical t) :- Identical t a)

    -- | Get the 'Unlift' action for the current transformer layer.
    --
    -- Since 0.1.0
    askUnlift :: forall t m. (MonadTransUnlift t, Monad m) => t m (Unlift t)
    askUnlift = liftWith unlifter
      where
        unlifter :: (forall n b. Monad n => t n b -> n (StT t b)) -> m (Unlift t)
        unlifter r = return $ Unlift (mkUnlift r)

    -- | A simplified version of 'askUnlift' which addresses the common case where
    -- polymorphism isn't necessary.
    --
    -- Since 0.1.0
    askRun :: (MonadTransUnlift t, Monad (t m), Monad m) => t m (t m a -> m a)
    askRun = liftM unlift askUnlift
    {-# INLINE askRun #-}

    -- | Similar to 'Unlift', but instead of moving one layer down the stack, moves
    -- the action to the base monad.
    --
    -- Since 0.1.0
    newtype UnliftBase b m = UnliftBase { unliftBase :: forall a. m a -> b a }

    class    (StM m a ~ a) => IdenticalBase m a
    instance (StM m a ~ a) => IdenticalBase m a

    -- | A monad transformer stack which can be unlifted, obeying the monad morphism laws.
    --
    -- Since 0.1.0
    class (MonadBaseControl b m, Forall (IdenticalBase m)) => MonadBaseUnlift b m | m -> b
    instance (MonadBaseControl b m, Forall (IdenticalBase m)) => MonadBaseUnlift b m

    mkUnliftBase :: forall m a b. (Forall (IdenticalBase m), Monad b)
                 => (forall c. m c -> b (StM m c)) -> m a -> b a
    mkUnliftBase r act = r act \\ (inst :: Forall (IdenticalBase m) :- IdenticalBase m a)

    -- | Get the 'UnliftBase' action for the current transformer stack.
    --
    -- Since 0.1.0
    askUnliftBase :: forall b m. (MonadBaseUnlift b m) => m (UnliftBase b m)
    askUnliftBase = liftBaseWith unlifter
      where
        unlifter :: (forall c. m c -> b (StM m c)) -> b (UnliftBase b m)
        unlifter r = return $ UnliftBase (mkUnliftBase r)

    -- | A simplified version of 'askUnliftBase' which addresses the common case
    -- where polymorphism isn't necessary.
    --
    -- Since 0.1.0
    askRunBase :: (MonadBaseUnlift b m)
               => m (m a -> b a)
    askRunBase = liftM unliftBase askUnliftBase
    {-# INLINE askRunBase #-}