{-# LANGUAGE CPP #-}
#if defined(__GLASGOW_HASKELL__)
{-# LANGUAGE DefaultSignatures #-}
#endif
{-# OPTIONS -fno-warn-deprecations #-}
-----------------------------------------------------------------------------
-- |
-- Copyright   :  (C) 2012-2015 Edward Kmett
-- License     :  BSD-style (see the file LICENSE)
--
-- Maintainer  :  Edward Kmett <ekmett@gmail.com>
-- Stability   :  experimental
-- Portability :  portable
--
-- This module provides the 'Bound' class, for performing substitution into
-- things that are not necessarily full monad transformers.
----------------------------------------------------------------------------
module Bound.Class
  ( Bound(..)
  , (=<<<)
  ) where

import Control.Monad.Trans.Class
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid
#endif
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Error
import Control.Monad.Trans.Identity
import Control.Monad.Trans.List
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.RWS
import Control.Monad.Trans.Reader
import Control.Monad.Trans.State
import Control.Monad.Trans.Writer

infixl 1 >>>=

-- | Instances of 'Bound' generate left modules over monads.
--
-- This means they should satisfy the following laws:
--
-- @
-- m '>>>=' 'return' ≡ m
-- m '>>>=' (λ x → k x '>>=' h) ≡ (m '>>>=' k) '>>>=' h
-- @
--
-- This guarantees that a typical Monad instance for an expression type
-- where Bound instances appear will satisfy the Monad laws (see doc/BoundLaws.hs).
--
-- If instances of 'Bound' are monad transformers, then @m '>>>=' f ≡ m '>>=' 'lift' '.' f@
-- implies the above laws, and is in fact the default definition.
--
-- This is useful for types like expression lists, case alternatives,
-- schemas, etc. that may not be expressions in their own right, but often
-- contain expressions.
--
-- /Note:/ 'Control.Monad.Free.Free' isn't "really" a monad transformer, even if
-- the kind matches. Therefore there isn't @'Bound' 'Control.Monad.Free.Free'@ instance.
class Bound t where
  -- | Perform substitution
  --
  -- If @t@ is an instance of @MonadTrans@ and you are compiling on GHC >= 7.4, then this
  -- gets the default definition:
  --
  -- @m '>>>=' f = m '>>=' 'lift' '.' f@
  (>>>=) :: Monad f => t f a -> (a -> f c) -> t f c
#if defined(__GLASGOW_HASKELL__)
  default (>>>=) :: (MonadTrans t, Monad f, Monad (t f)) =>
                    t f a -> (a -> f c) -> t f c
  m >>>= f = m >>= lift . f
  {-# INLINE (>>>=) #-}
#endif

instance Bound (ContT c) where
  m >>>= f = m >>= lift . f
  {-# INLINE (>>>=) #-}

instance Error e => Bound (ErrorT e) where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

instance Bound IdentityT where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

instance Bound ListT where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

instance Bound MaybeT where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

instance Monoid w => Bound (RWST r w s) where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

instance Bound (ReaderT r) where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

instance Bound (StateT s) where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

instance Monoid w => Bound (WriterT w) where
 m >>>= f = m >>= lift . f
 {-# INLINE (>>>=) #-}

infixr 1 =<<<
-- | A flipped version of ('>>>=').
--
-- @('=<<<') = 'flip' ('>>>=')@
(=<<<) :: (Bound t, Monad f) => (a -> f c) -> t f a -> t f c
(=<<<) = flip (>>>=)
{-# INLINE (=<<<) #-}