{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
module Newtype.Union(
module Data.Type.Coercion.Related,
IsUnion(..),
withUnion,
unique, greater, idemp, commutative, associative
) where
import Prelude hiding (id, (.))
import Control.Category
import Data.Type.Coercion.Sub (sub, equiv)
import Data.Type.Coercion.Sub.Internal
import Data.Type.Coercion.Related
import Data.Type.Coercion.Related.Internal
import Data.Type.Coercion ( Coercion(Coercion), TestCoercion(..))
-- | @IsUnion x y z@ witnesses the fact:
--
-- * All @x, y, z@ share the same runtime representation
-- * @z@ is a union type of @x@ and @y@. In other words, the following three holds:
--
-- * @'Sub' x z@
-- * @Sub y z@
-- * For any type @r@ satisfying both of @(Sub x r, Sub y r)@, @Sub z r@.
data IsUnion x y z = IsUnion
{
inl :: !(Sub x z), -- ^ @x@ can be safely coerced to @z@
inr :: !(Sub y z), -- ^ @y@ can be safely coerced to @z@
elim :: forall r. Sub x r -> Sub y r -> Sub z r
-- ^ Given both @x@ and @y@ can be safely coerced to @r@, too @z@ can.
}
instance Eq (IsUnion x y z) where
_ == _ = True
instance Ord (IsUnion x y z) where
compare _ _ = EQ
instance TestCoercion (IsUnion x y) where
testCoercion u1 u2 = Just (unique u1 u2)
-- | For a pair of 'Related' types @x@ and @y@, make some (existentially quantified)
-- type @xy@ where @xy@ is a union type of @x, y@.
withUnion :: Related x y -> (forall xy. IsUnion x y xy -> r) -> r
withUnion (Related Coercion) body =
body IsUnion{ inl = sub, inr = id, elim = seq }
-- | Two union types @z,z'@ of the same pair of types @x,y@ may be different,
-- but they are equivalent in terms of coercibility.
unique :: IsUnion x y z -> IsUnion x y z' -> Coercion z z'
unique xy xy' = equiv (elim xy (inl xy') (inr xy')) (elim xy' (inl xy) (inr xy))
-- | When @Sub x y@, @y@ itself is a union type of @x, y@.
greater :: Sub x y -> IsUnion x y y
greater l = IsUnion{ inl = l, inr = id, elim=seq }
-- | Union is idempotent.
--
-- Note: combining @idemp@ and 'unique', @IsUnion x x z -> Coercion x z@ holds.
idemp :: IsUnion x x x
idemp = greater id
-- | Union is commutative.
--
-- Note: combining @commutative@ and 'unique', @IsUnion x y xy -> IsUnion y x yx -> Coercion xy yx@ holds.
commutative :: IsUnion x y z -> IsUnion y x z
commutative xyz = IsUnion{ inl = inr xyz, inr = inl xyz, elim = flip (elim xyz) }
-- | Union is associative.
--
-- Note: combining @associative@ and 'unique', the following holds.
--
-- > IsUnion x y xy -> IsUnion xy z xy'z
-- > -> IsUnion y z yz -> IsUnion x yz x'yz
-- > -> Coercion xy'z x'yz
associative :: IsUnion x y xy -> IsUnion xy z xyz -> IsUnion y z yz -> IsUnion x yz xyz
associative xy xy'z yz =
IsUnion {
inl = inl xy'z . inl xy
, inr = elim yz (inl xy'z . inr xy) (inr xy'z)
, elim = \x_r yz_r -> elim xy'z (elim xy x_r (yz_r . inl yz)) (yz_r . inr yz)
}