{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
module Savage.Internal.Distributive (
    Distributive(..)
  ) where

import           Control.Monad (join)
import           Control.Monad.Morph (MFunctor(..))
import           Control.Monad.Trans.Class (MonadTrans(..))
import           Control.Monad.Trans.Except (ExceptT(..), runExceptT)
import           Control.Monad.Trans.Maybe (MaybeT(..))
import           Control.Monad.Trans.Reader (ReaderT(..))
import           Control.Monad.Trans.Writer (WriterT(..))

import           GHC.Exts (Constraint)


class Distributive g where
  type Transformer
    (f :: (* -> *) -> * -> *)
    (g :: (* -> *) -> * -> *)
    (m :: * -> *) :: Constraint

  type Transformer f g m = (
        Monad m
      , Monad (f m)
      , Monad (g m)
      , Monad (f (g m))
      , MonadTrans f
      , MFunctor f
      )

  -- | Distribute one monad transformer over another.
  --
  distribute :: Transformer f g m => g (f m) a -> f (g m) a

instance Distributive MaybeT where
  distribute x =
    lift . MaybeT . pure =<< hoist lift (runMaybeT x)

instance Distributive (ExceptT x) where
  distribute x =
    lift . ExceptT . pure =<< hoist lift (runExceptT x)

instance Monoid w => Distributive (WriterT w) where
  distribute x =
    lift . WriterT . pure =<< hoist lift (runWriterT x)

instance Distributive (ReaderT r) where
  distribute x =
    join . lift . ReaderT $ \r ->
      pure . hoist lift $ runReaderT x r