eq-4.3: src/Data/Eq/Type/Hetero.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
-----------------------------------------------------------------------------
-- |
-- Module : Data.Eq.Type.Hetero
-- Copyright : (C) 2011-2014 Edward Kmett, 2018 Ryan Scott
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : Edward Kmett <ekmett@gmail.com>
-- Stability : provisional
-- Portability : GHC
--
-- Leibnizian equality à la "Data.Eq.Type", generalized to be heterogeneous
-- using higher-rank kinds.
--
-- This module is only exposed on GHC 8.2 and later.
----------------------------------------------------------------------------
module Data.Eq.Type.Hetero
(
-- * Heterogeneous Leibnizian equality
(:==)(..)
-- * Equality as an equivalence relation
, refl
, trans
, symm
, coerce
, apply
-- * Lifting equality
, lift
, lift2, lift2'
, lift3, lift3'
-- * Lowering equality
, lower
, lower2
, lower3
-- * 'ET.:=' equivalence
-- | 'ET.:=' is equivalent in power
, toHomogeneous
, fromHomogeneous
-- * 'Eq.:~:' equivalence
-- | 'Eq.:~:' is equivalent in power
, fromLeibniz
, toLeibniz
-- * 'Eq.:~~:' equivalence
-- | 'Eq.:~~:' is equivalent in power
, heteroFromLeibniz
, heteroToLeibniz
-- * 'Co.Coercion' conversion
-- | Leibnizian equality can be converted to representational equality
, reprLeibniz
) where
import Control.Category
import Data.Groupoid
import Data.Semigroupoid
import qualified Data.Type.Coercion as Co
import qualified Data.Type.Equality as Eq
import Data.Kind
import qualified Data.Eq.Type as ET
import Prelude hiding (id, (.))
infixl 4 :==
-- | Heterogeneous Leibnizian equality.
--
-- Leibnizian equality states that two things are equal if you can
-- substitute one for the other in all contexts.
newtype (a :: j) :== (b :: k)
= HRefl { hsubst :: forall (c :: forall i. i -> Type). c a -> c b }
type role (:==) nominal nominal
-- | Equality is reflexive.
refl :: a :== a
refl = HRefl id
data family Coerce :: forall k. k -> Type
newtype instance Coerce (a :: Type) = Coerce { uncoerce :: a }
-- | If two things are equal, you can convert one to the other.
coerce :: a :== b -> a -> b
coerce f = uncoerce . hsubst f . Coerce
newtype Pair1 :: forall j1 k1 j2.
j1 -> k1 -> j2
-> forall k2. k2 -> Type where
Pair1 :: { unpair1 :: '(a1, a2) :== '(b1, b2) } -> Pair1 a1 b1 a2 b2
newtype Pair2 :: forall j2 k2 j1.
j2 -> k2 -> j1
-> forall k1. k1 -> Type where
Pair2 :: { unpair2 :: '(a1, a2) :== '(b1, b2) } -> Pair2 a2 b2 a1 b1
-- | Lift two equalities pairwise.
pair :: a1 :== b1 -> a2 :== b2 -> '(a1, a2) :== '(b1, b2)
pair ab1 ab2 = unpair2 $ hsubst ab1 $ Pair2 $ unpair1 $ hsubst ab2 $ Pair1 refl
data family Apply :: forall j1 j2.
(j1 -> j2) -> j1
-> forall k. k -> Type
newtype instance Apply (f :: j1 -> j2) (a :: j1) '((g :: k1 -> k2), (b :: k1))
= Apply { unapply :: f a :== g b }
-- | Apply one equality to another, respectively
apply :: f :== g -> a :== b -> f a :== g b
apply fg ab = unapply $ hsubst (pair fg ab) $ Apply refl
newtype Push :: (forall j k. j -> k -> Type)
-> forall j. j -> forall k. k -> Type where
Push :: forall (p :: forall j k. j -> k -> Type)
j k (a :: j) (b :: k).
{ unpush :: p a b } -> Push p a b
-- | Equality is compositional.
comp :: b :== c -> a :== b -> a :== c
comp f = unpush . hsubst f . Push
-- | Equality forms a category.
instance Category (:==) where
id = refl
(.) = comp
instance Semigroupoid (:==) where
o = comp
instance Groupoid (:==) where
inv = symm
-- | Equality is transitive.
trans :: a :== b -> b :== c -> a :== c
trans = flip comp
newtype Symm :: (forall j. j -> forall k. k -> Type)
-> forall j. j -> forall k. k -> Type where
Symm :: forall (p :: forall j. j -> forall k. k -> Type)
j k (a :: j) (b :: k).
{ unsymm :: p b a } -> Symm p a b
-- | Equality is symmetric.
symm :: a :== b -> b :== a
symm ab = unpush $ unsymm $ hsubst ab $ Symm $ Push refl
data family Lift :: forall j r. (j -> r) -> j
-> forall k. k
-> Type
newtype instance Lift f (a :: j) (b :: j) =
Lift { unlift :: f a :== f b }
-- | You can lift equality into any type constructor...
lift :: a :== b -> f a :== f b
lift f = unlift $ hsubst f $ Lift refl
data family Lift2 :: forall j1 j2 r.
(j1 -> j2 -> r) -> j1 -> j2
-> forall k. k
-> Type
newtype instance Lift2 f (a :: j1) (c :: j2) (b :: j1) =
Lift2 { unlift2 :: f a c :== f b c }
-- | ... in any position.
lift2 :: a :== b -> f a c :== f b c
lift2 f = unlift2 $ hsubst f $ Lift2 refl
lift2' :: a :== b -> c :== d -> f a c :== f b d
lift2' ab cd = unpush $ lift2 ab `hsubst` Push (lift cd)
data family Lift3 :: forall j1 j2 j3 r.
(j1 -> j2 -> j3 -> r) -> j1 -> j2 -> j3
-> forall k. k
-> Type
newtype instance Lift3 f (a :: j1) (c :: j2) (d :: j3) (b :: j1) =
Lift3 { unlift3 :: f a c d :== f b c d }
lift3 :: a :== b -> f a c d :== f b c d
lift3 f = unlift3 $ hsubst f $ Lift3 refl
lift3' :: a :== b -> c :== d -> e :== f -> g a c e :== g b d f
lift3' ab cd ef = unpush $ unpush (lift3 ab `hsubst` Push (lift2 cd)) `hsubst` Push (lift ef)
data family Lower :: forall j. j
-> forall k. k
-> Type
newtype instance Lower a (f x) = Lower { unlower :: a :== x }
-- | Type constructors are generative and injective, so you can lower equality
-- through any type constructors.
lower :: forall a b f g. f a :== g b -> a :== b
lower f = unlower $ hsubst f (Lower refl :: Lower a (f a))
data family Lower2 :: forall j. j
-> forall k. k
-> Type
newtype instance Lower2 a (f x c) = Lower2 { unlower2 :: a :== x }
lower2 :: forall a b f g c c'. f a c :== g b c' -> a :== b
lower2 f = unlower2 $ hsubst f (Lower2 refl :: Lower2 a (f a c))
data family Lower3 :: forall j. j
-> forall k. k
-> Type
newtype instance Lower3 a (f x c d) = Lower3 { unlower3 :: a :== x }
lower3 :: forall a b f g c c' d d'. f a c d :== g b c' d' -> a :== b
lower3 f = unlower3 $ hsubst f (Lower3 refl :: Lower3 a (f a c d))
data family Flay :: forall j.
(j -> j -> Type) -> j
-> forall k. k
-> Type
newtype instance Flay p (a :: j) (b :: j) = Flay { unflay :: p a b }
-- | Convert an appropriately kinded heterogeneous Leibnizian equality into
-- a homogeneous Leibnizian equality '(ET.:=)'.
toHomogeneous :: a :== b -> a ET.:= b
toHomogeneous f = unflay $ hsubst f $ Flay ET.refl
-- | Convert a homogeneous Leibnizian equality '(ET.:=)' to an appropriately kinded
-- heterogeneous Leibizian equality.
fromHomogeneous :: a ET.:= b -> a :== b
fromHomogeneous f = ET.subst f refl
fromLeibniz :: forall a b. a :== b -> a Eq.:~: b
fromLeibniz f = unflay $ hsubst f $ Flay Eq.Refl
toLeibniz :: a Eq.:~: b -> a :== b
toLeibniz Eq.Refl = refl
heteroFromLeibniz :: a :== b -> a Eq.:~~: b
heteroFromLeibniz f = unpush $ hsubst f $ Push Eq.HRefl
heteroToLeibniz :: a Eq.:~~: b -> a :== b
heteroToLeibniz Eq.HRefl = refl
instance Eq.TestEquality ((:==) a) where
testEquality fa fb = Just (fromLeibniz (trans (symm fa) fb))
reprLeibniz :: a :== b -> Co.Coercion a b
reprLeibniz f = unflay $ hsubst f $ Flay Co.Coercion
instance Co.TestCoercion ((:==) a) where
testCoercion fa fb = Just (reprLeibniz (trans (symm fa) fb))