{-# LANGUAGE TypeFamilies, FlexibleContexts, FlexibleInstances #-}
{-# OPTIONS_GHC -fenable-rewrite-rules #-}

----------------------------------------------------------------------
-- |
-- Module      :  Data.Functor.Corepresentable
-- Copyright   :  (c) Edward Kmett 2011
-- License     :  BSD3
-- 
-- Maintainer  :  ekmett@gmail.com
-- Stability   :  experimental
-- 
-- Representable contravariant endofunctors over the category of Haskell 
-- types are isomorphic to @(_ -> r)@ and resemble mappings to a
-- fixed range.
----------------------------------------------------------------------

module Data.Functor.Corepresentable
  ( 
  -- * Values
    Value
  -- * Contravariant Keyed
  , Valued(..)
  -- * Contravariant Indexed
  , Coindexed(..)
  -- * Representable Contravariant Functors
  , Corepresentable(..)
  -- * Default definitions
  , contramapDefault
  , contramapWithValueDefault
  ) where

import Control.Monad.Reader
import Data.Functor.Contravariant
import Data.Functor.Product
import Data.Functor.Coproduct
import Prelude hiding (lookup)

type family Value (f :: * -> *)

-- | Dual to 'Keyed'.
class Contravariant f => Valued f where
  contramapWithValue :: (b -> Either a (Value f)) -> f a -> f b

-- | Dual to 'Indexed'.
class Coindexed f where
  coindex :: f a -> a -> Value f

-- | A 'Functor' @f@ is 'Corepresentable' if 'corep' and 'coindex' witness an isomorphism to @(_ -> Value f)@.
--
-- > tabulate . index = id
-- > index . tabulate = id
-- > tabulate . return f = return f

class (Coindexed f, Valued f) => Corepresentable f where
  -- | > contramap f (corep g) = corep (g . f)
  corep :: (a -> Value f) -> f a

{-# RULES
"corep/coindex" forall t. corep (coindex t) = t
 #-}

-- * Default definitions

contramapDefault :: Corepresentable f => (a -> b) -> f b -> f a
contramapDefault f = corep . (. f) . coindex 

contramapWithValueDefault :: Corepresentable f => (b -> Either a (Value f)) -> f a -> f b
contramapWithValueDefault f p = corep $ either (coindex p) id . f

-- * Dual arrows

type instance Value (Op r) = r

instance Valued (Op r) where
  contramapWithValue = contramapWithValueDefault

instance Coindexed (Op r) where
  coindex = getOp

instance Corepresentable (Op r) where
  corep = Op

-- * Predicates

type instance Value Predicate = Bool

instance Valued Predicate where
  contramapWithValue = contramapWithValueDefault

instance Coindexed Predicate where
  coindex = getPredicate

instance Corepresentable Predicate where
  corep = Predicate

-- * Products

type instance Value (Product f g) = (Value f, Value g)

instance (Valued f, Valued g) => Valued (Product f g) where
  -- contramapWithValue :: (b -> Either a (Value f)) -> f a -> f b
  contramapWithValue h (Pair f g) = Pair 
      (contramapWithValue (fmap fst . h) f)
      (contramapWithValue (fmap snd . h) g)
      -- (contramapWithValue (either id snd . h) g)
      -- (either g snd . h)

instance (Coindexed f, Coindexed g) => Coindexed (Product f g) where
  coindex (Pair f g) a = (coindex f a, coindex g a)

instance (Corepresentable f, Corepresentable g) => Corepresentable (Product f g) where
  corep f = Pair (corep (fst . f)) (corep (snd . f))


-- * Coproducts

type instance Value (Coproduct f g) = Either (Value f) (Value g)

instance (Coindexed f, Coindexed g) => Coindexed (Coproduct f g) where
  coindex (Coproduct (Left f)) a  = Left  $ coindex f a 
  coindex (Coproduct (Right g)) a = Right $ coindex g a