{-# OPTIONS_GHC -cpp -fglasgow-exts -fallow-undecidable-instances #-}
-------------------------------------------------------------------------------------------
-- |
-- Module	: Control.Functor.Combinators.Lift
-- Copyright 	: 2008 Edward Kmett
-- License	: BSD
--
-- Maintainer	: Edward Kmett <ekmett@gmail.com>
-- Stability	: experimental
-- Portability	: non-portable (functional-dependencies)
--
-- transform a pair of functors with a bifunctor deriving a new functor.
-- this subsumes functor product and functor coproduct
-------------------------------------------------------------------------------------------

module Control.Functor.Combinators.Lift 
	( Lift(Lift,runLift)
	, (:*:), runProductF
	, (:+:), runCoproductF 
	, Ap, runAp, mkAp
	) where

import Control.Category.Hask
import Control.Functor
import Control.Functor.Contra
import Control.Functor.Exponential
import Control.Functor.Full
import Control.Functor.HigherOrder
import Control.Monad.Identity
import Control.Functor.Pointed
import Control.Arrow ((&&&),(|||))

-- * Bifunctor functor transformer

-- type-level LiftA2 
newtype Lift p f g a = Lift { runLift :: p (f a) (g a) }
type Ap p = Lift p Identity

runAp :: Bifunctor p Hask Hask Hask => Ap p f a -> p a (f a)
runAp = first runIdentity . runLift

mkAp :: Bifunctor p Hask Hask Hask => p a (f a) -> Ap p f a 
mkAp = Lift . first Identity

instance (Bifunctor p Hask Hask Hask, Functor f ,Functor g) => Functor (Lift p f g) where
        fmap f = Lift . bimap (fmap f) (fmap f) . runLift

instance (Bifunctor p Hask Hask Hask, ContraFunctor f ,ContraFunctor g) => ContraFunctor (Lift p f g) where
        contramap f = Lift . bimap (contramap f) (contramap f) . runLift

instance (Bifunctor p Hask Hask Hask, ExpFunctor f ,ExpFunctor g) => ExpFunctor (Lift p f g) where
        xmap f g = Lift . bimap (xmap f g) (xmap f g) . runLift

instance (Bifunctor p Hask Hask Hask) => HFunctor (Ap p) where
        ffmap f = Lift . bimap (fmap f) (fmap f) . runLift
        hfmap f = Lift . second f . runLift


type (f :*: g) = Lift (,) f g

runProductF :: (f :*: g) a -> (f a, g a)
runProductF = runLift

instance (Pointed f, Pointed g) => Pointed (f :*: g) where
        point = Lift . (point &&& point)

instance (Faithful f, Faithful g) => Faithful (f :*: g)

type (f :+: g) = Lift Either f g

runCoproductF :: (f :+: g) a -> Either (f a) (g a)
runCoproductF = runLift

instance (Copointed f, Copointed g) => Copointed (f :+: g) where
        extract = (extract ||| extract) . runLift