{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}

#if __GLASGOW_HASKELL__ >= 704
{-# LANGUAGE Safe #-}
#elif __GLASGOW_HASKELL__ >= 702
{-# LANGUAGE Trustworthy #-}
#endif

#if __GLASGOW_HASKELL__ >= 706
{-# LANGUAGE PolyKinds #-}
#endif

module Data.Bifunctor.Functor
  ( (:->)
  , BifunctorFunctor(..)
  , BifunctorMonad(..)
  , biliftM
  , BifunctorComonad(..)
  , biliftW
  ) where

-- | Using parametricity as an approximation of a natural transformation in two arguments.
type (:->) p q = forall a b. p a b -> q a b
infixr 0 :->

class BifunctorFunctor t where
  bifmap :: (p :-> q) -> t p :-> t q

class BifunctorFunctor t => BifunctorMonad t where
  bireturn :: p :-> t p
  bibind   :: (p :-> t q) -> t p :-> t q
  bibind f = bijoin . bifmap f
  bijoin   :: t (t p) :-> t p
  bijoin = bibind id
#if __GLASGOW_HASKELL__ >= 708
  {-# MINIMAL bireturn, (bibind | bijoin) #-}
#endif

biliftM :: BifunctorMonad t => (p :-> q) -> t p :-> t q
biliftM f = bibind (bireturn . f)
{-# INLINE biliftM #-}

class BifunctorFunctor t => BifunctorComonad t where
  biextract :: t p :-> p
  biextend :: (t p :-> q) -> t p :-> t q
  biextend f = bifmap f . biduplicate
  biduplicate :: t p :-> t (t p)
  biduplicate =  biextend id
#if __GLASGOW_HASKELL__ >= 708
  {-# MINIMAL biextract, (biextend | biduplicate) #-}
#endif

biliftW :: BifunctorComonad t => (p :-> q) -> t p :-> t q
biliftW f = biextend (f . biextract)
{-# INLINE biliftW #-}