module Data.Functor.Invariant where

import Prelude
import Control.Applicative (ZipList)
import Data.Functor.Contravariant
import Data.Functor.Compose
import Data.Functor.Identity
import Data.Functor.Sum
import Data.Functor.Product
import Data.List.NonEmpty (NonEmpty)

class Invariant f where
  invmap :: (a -> a') -> (a' -> a) -> f a -> f a'

-- Invariant witnesses an Isomorphism
data Iso a b = Iso (a -> b) (b -> a)

invIso :: Invariant f => Iso a a' -> Iso (f a) (f a')
invIso :: Iso a a' -> Iso (f a) (f a')
invIso (Iso a -> a'
f a' -> a
g)  = (f a -> f a') -> (f a' -> f a) -> Iso (f a) (f a')
forall a b. (a -> b) -> (b -> a) -> Iso a b
Iso ((a -> a') -> (a' -> a) -> f a -> f a'
forall (f :: * -> *) a a'.
Invariant f =>
(a -> a') -> (a' -> a) -> f a -> f a'
invmap a -> a'
f a' -> a
g) ((a' -> a) -> (a -> a') -> f a' -> f a
forall (f :: * -> *) a a'.
Invariant f =>
(a -> a') -> (a' -> a) -> f a -> f a'
invmap a' -> a
g a -> a'
f)

newtype FromFunctor f a = FromFunctor { FromFunctor f a -> f a
runBi :: f a }

instance Functor f => Invariant (FromFunctor f) where
  invmap :: (a -> a') -> (a' -> a) -> FromFunctor f a -> FromFunctor f a'
  invmap :: (a -> a') -> (a' -> a) -> FromFunctor f a -> FromFunctor f a'
invmap a -> a'
f a' -> a
_ = f a' -> FromFunctor f a'
forall (f :: * -> *) a. f a -> FromFunctor f a
FromFunctor (f a' -> FromFunctor f a')
-> (FromFunctor f a -> f a') -> FromFunctor f a -> FromFunctor f a'
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> a') -> f a -> f a'
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> a'
f (f a -> f a')
-> (FromFunctor f a -> f a) -> FromFunctor f a -> f a'
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FromFunctor f a -> f a
forall (f :: * -> *) a. FromFunctor f a -> f a
runBi

newtype FromContra f a = FromContra { FromContra f a -> f a
runContra :: f a }

instance Contravariant f => Invariant (FromContra f) where
  invmap :: (a -> a') -> (a' -> a) -> FromContra f a -> FromContra f a'
  invmap :: (a -> a') -> (a' -> a) -> FromContra f a -> FromContra f a'
invmap a -> a'
_ a' -> a
g = f a' -> FromContra f a'
forall (f :: * -> *) a. f a -> FromContra f a
FromContra (f a' -> FromContra f a')
-> (FromContra f a -> f a') -> FromContra f a -> FromContra f a'
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a' -> a) -> f a -> f a'
forall (f :: * -> *) a b. Contravariant f => (a -> b) -> f b -> f a
contramap a' -> a
g (f a -> f a') -> (FromContra f a -> f a) -> FromContra f a -> f a'
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FromContra f a -> f a
forall (f :: * -> *) a. FromContra f a -> f a
runContra

deriving via FromFunctor Identity           instance Invariant Identity
deriving via FromFunctor (Compose f g)      instance (Functor f, Functor g) => Invariant (Compose f g)
deriving via FromFunctor []                 instance Invariant []
deriving via FromFunctor ZipList            instance Invariant ZipList
deriving via FromFunctor NonEmpty           instance Invariant NonEmpty
deriving via FromFunctor Maybe              instance Invariant Maybe
deriving via FromFunctor (Either e)         instance Invariant (Either e)
deriving via FromFunctor IO                 instance Invariant IO
deriving via FromFunctor (Sum f g)          instance (Functor f, Functor g) => Invariant (Sum f g)
deriving via FromFunctor (Product f g)      instance (Functor f, Functor g) => Invariant (Product f g)
deriving via (FromFunctor ((,) x1))         instance Invariant ((,) x1)
deriving via (FromFunctor ((,,) x1 x2))     instance Invariant ((,,) x1 x2)
deriving via (FromFunctor ((,,,) x1 x2 x3)) instance Invariant ((,,,) x1 x2 x3)