packages feed

proarrow-0.1.0.0: src/Proarrow/Colimit/BinaryCoproduct.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE IncoherentInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | Binary coproducts: 'HasBinaryCoproducts' provides @a '||' b@ with injections 'lft'\/'rgt' and
-- copairing @('|||')@, and 'HasCoproducts' adds the initial object. Also biproducts ('HasBiproducts')
-- and the 'COPROD' kind wrapper, which makes @('||')@ the tensor of a monoidal structure on the same
-- objects.
module Proarrow.Colimit.BinaryCoproduct where

import Data.Kind (Type)
import Prelude (Show, ($), type (~))
import Prelude qualified as P

import Proarrow.Category.Instance.Bool (BOOL (..), Booleans (..))
import Proarrow.Category.Instance.Free
  ( Elem (..)
  , FREE (..)
  , HasStructure (..)
  , IsFreeOb (..)
  , Lower
  , WithShow
  , withLowerOb
  )
import Proarrow.Category.Instance.Free qualified as F
import Proarrow.Category.Instance.Opposite (OPPOSITE (..), Op (..))
import Proarrow.Category.Instance.Product (Diag, Fst, Snd, (:**:) (..))
import Proarrow.Category.Instance.Prof (Prof (..))
import Proarrow.Category.Instance.Unit qualified as U
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..))
import Proarrow.Colimit.Initial (HasInitialObject (..))
import Proarrow.Core (CAT, CategoryOf (..), Hom, Profunctor (..), Promonad (..), UN, WrappedOb, type (+->))
import Proarrow.Functor (Functor (..), FunctorForRep (..))
import Proarrow.Limit.BinaryProduct (HasBinaryProducts (..), PROD (..), Prod (..), diag)
import Proarrow.Limit.Terminal (HasTerminalObject (..))
import Proarrow.Object (Obj, obj, tgt)
import Proarrow.Profunctor.Corepresentable (Corepresentable (..), withObCorep)
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Instance.Coproduct (coproduct, (:+:) (..))
import Proarrow.Profunctor.Instance.Identity (Id (..))
import Proarrow.Profunctor.Instance.Product ((:*:) (..))
import Proarrow.Profunctor.Instance.Terminal (TerminalProfunctor (..))
import Proarrow.Profunctor.Representable (CorepStar (..), Rep (..), Representable (..))
import Proarrow.Tools.Laws (Law (..), Laws (..), (===))

infixl 4 ||
infixl 4 |||
infixl 4 +++

-- | Binary coproducts, dual to 'Proarrow.Limit.BinaryProduct.HasBinaryProducts': an object
-- @a '||' b@ with injections 'lft' and 'rgt', universal among all pairs of arrows into a common
-- target. Each such pair factors through it uniquely via '(|||)'.
--
-- __Laws:__
--
-- * @(f '|||' g) . 'lft' = f@
-- * @(f '|||' g) . 'rgt' = g@
-- * Uniqueness: @(h . f) '|||' (h . g) = h . (f '|||' g)@
--
-- Checked by 'Proarrow.Testing.Laws.testBinaryCoproducts'.
class (CategoryOf k) => HasBinaryCoproducts k where
  -- | The coproduct object.
  type (a :: k) || (b :: k) :: k

  -- | Recovers @'Ob' (a '||' b)@ from the objecthood of the summands.
  withObCoprod :: (Ob (a :: k), Ob b) => ((Ob (a || b)) => r) -> r

  -- | The left injection.
  lft :: (Ob (a :: k), Ob b) => a ~> (a || b)

  -- | The right injection.
  rgt :: (Ob (a :: k), Ob b) => b ~> (a || b)

  -- | The mediating arrow: case-splits two arrows into a common target.
  (|||) :: (x :: k) ~> a -> y ~> a -> (x || y) ~> a

  -- | The coproduct of two arrows, acting on each summand independently.
  (+++) :: forall a b x y. (a :: k) ~> x -> b ~> y -> a || b ~> x || y
  l +++ r = lft @k @x @y . l ||| rgt @k @x @y . r \\ l \\ r

lft' :: forall {k} (a :: k) a' b. (HasBinaryCoproducts k) => a ~> a' -> Obj b -> a ~> (a' || b)
lft' a b = lft @k @a' @b . a \\ a \\ b

rgt' :: forall {k} (a :: k) b b'. (HasBinaryCoproducts k) => Obj a -> b ~> b' -> b ~> (a || b')
rgt' a b = rgt @k @a @b' . b \\ a \\ b

left :: forall {k} (c :: k) (a :: k) (b :: k). (HasBinaryCoproducts k, Ob c) => a ~> b -> (a || c) ~> (b || c)
left f = f +++ obj @c

right :: forall {k} (c :: k) (a :: k) (b :: k). (HasBinaryCoproducts k, Ob c) => a ~> b -> (c || a) ~> (c || b)
right f = obj @c +++ f

codiag :: forall {k} (a :: k). (HasBinaryCoproducts k, Ob a) => (a || a) ~> a
codiag = id ||| id

swapCoprod' :: forall {k} (a :: k) a' b b'. (HasBinaryCoproducts k) => a ~> a' -> b ~> b' -> (a || b) ~> (b' || a')
swapCoprod' a b = rgt' (tgt b) a ||| lft' b (tgt a)

swapCoprod :: forall {k} (a :: k) b. (HasBinaryCoproducts k, Ob a, Ob b) => a || b ~> b || a
swapCoprod = swapCoprod' (obj @a) (obj @b)

-- | The coproduct as a functor from the product category, @'(a, b) ↦ a || b@. The coproduct
-- analogue of 'Proarrow.Category.Monoidal.MultRep'.
data PlusRep :: (k, k) +-> k

instance (HasBinaryCoproducts k) => FunctorForRep (PlusRep :: (k, k) +-> k) where
  type PlusRep @ '(a, b) = a || b
  fmap (f :**: g) = f +++ g

data family Coproduct :: k -> k +-> k
instance (HasBinaryCoproducts k, Ob a) => FunctorForRep (Coproduct a :: k +-> k) where
  type Coproduct a @ b = a || b
  fmap f = right @a f

type HasCoproducts k = (HasInitialObject k, HasBinaryCoproducts k)

class ((a ** b) ~ (a || b)) => TensorIsCoproduct a b
instance ((a ** b) ~ (a || b)) => TensorIsCoproduct a b
class
  (HasCoproducts k, Monoidal k, (Unit :: k) ~ InitialObject, forall (a :: k) (b :: k). TensorIsCoproduct a b) =>
  Cocartesian k
instance
  (HasCoproducts k, Monoidal k, (Unit :: k) ~ InitialObject, forall (a :: k) (b :: k). TensorIsCoproduct a b)
  => Cocartesian k

-- | Every functor between cocartesian categories is lax monoidal, @f a || f b ~> f (a || b)@ by the
-- injections and @InitialObject ~> f InitialObject@ by initiality. On the 'CorepStar' of its
-- corepresentable profunctor this is 'Proarrow.Category.Monoidal.LaxMonoidal'.
instance (Corepresentable p, Cocartesian j, Cocartesian k) => MonoidalProfunctor (CorepStar (p :: j +-> k)) where
  one = withObCorep @p @Unit (CorepStar initiate)
  CorepStar @a f ** CorepStar @b g = withOb2 @k @a @b (CorepStar (parCorepCocartesian @p @a @b f g))

parCorepCocartesian
  :: forall {j} {k} p (a :: k) b a' b'
   . ( Corepresentable (p :: j +-> k)
     , Cocartesian j
     , Cocartesian k
     , TensorIsCoproduct a b
     , TensorIsCoproduct a' b'
     , Ob a
     , Ob b
     )
  => (a' ~> p %% a) -> (b' ~> p %% b) -> (a' ** b') ~> p %% (a ** b)
parCorepCocartesian f g = corepMap @p (lft @k @a @b) . f ||| corepMap @p (rgt @k @a @b) . g

instance HasBinaryCoproducts Type where
  type a || b = P.Either a b
  withObCoprod r = r
  lft = P.Left
  rgt = P.Right
  (|||) = P.either

instance HasBinaryCoproducts () where
  -- a wildcard, not @'()@, so that @a || b@ reduces for an abstract @a@, as on pairs
  type _ || _ = '()
  withObCoprod r = r
  lft = U.Unit
  rgt = U.Unit
  U.Unit ||| U.Unit = U.Unit

instance HasBinaryCoproducts BOOL where
  type FLS || b = b
  type TRU || b = TRU
  type a || FLS = a
  type a || TRU = TRU
  withObCoprod @a r = case obj @a of
    Tru -> r
    Fls -> r
  lft @a @b = case obj @a of
    Fls -> initiate @_ @b
    Tru -> Tru
  rgt @a @b = case obj @b of
    Fls -> initiate @_ @a
    Tru -> Tru
  Fls ||| Fls = Fls
  F2T ||| b = b
  Tru ||| _ = Tru

-- | Coproducts in a product category are componentwise. Through the projections, as products are
-- there, so that @a || b@ reduces for an abstract pair.
instance (HasBinaryCoproducts j, HasBinaryCoproducts k) => HasBinaryCoproducts (j, k) where
  type a || b = '(Fst @ a || Fst @ b, Snd @ a || Snd @ b)
  withObCoprod @'(a1, a2) @'(b1, b2) r = withObCoprod @j @a1 @b1 (withObCoprod @k @a2 @b2 r)
  lft @'(a1, a2) @'(b1, b2) = lft @_ @a1 @b1 :**: lft @_ @a2 @b2
  rgt @'(a1, a2) @'(b1, b2) = rgt @_ @a1 @b1 :**: rgt @_ @a2 @b2
  (f1 :**: f2) ||| (g1 :**: g2) = (f1 ||| g1) :**: (f2 ||| g2)

instance (CategoryOf j, CategoryOf k) => HasBinaryCoproducts (j +-> k) where
  type p || q = p :+: q
  withObCoprod r = r
  lft = Prof InjL
  rgt = Prof InjR
  Prof l ||| Prof r = Prof (coproduct l r)

instance (HasBinaryCoproducts j, Corepresentable (p :: j +-> k), Corepresentable q) => Corepresentable (p :*: q) where
  type (p :*: q) %% a = (p %% a) || (q %% a)
  coindex (p :*: q) = coindex p ||| coindex q
  cotabulate @a f =
    withObCorep @p @a
      (withObCorep @q @a (cotabulate (f . lft @_ @(p %% a) @(q %% a)) :*: cotabulate (f . rgt @_ @(p %% a) @(q %% a))))
  corepMap f = corepMap @p f +++ corepMap @q f

instance (HasBinaryCoproducts k) => HasBinaryCoproducts (PROD k) where
  type PR a || PR b = PR (a || b)
  withObCoprod @(PR a) @(PR b) r = withObCoprod @k @a @b r
  lft @(PR a) @(PR b) = Prod (lft @_ @a @b)
  rgt @(PR a) @(PR b) = Prod (rgt @_ @a @b)
  Prod l ||| Prod r = Prod (l ||| r)

type data COPROD k = COPR k

-- | Lifts a profunctor to the 'COPROD'-wrapped kinds, where the monoidal structure is the
-- coproduct.
type Coprod :: j +-> k -> COPROD j +-> COPROD k
data Coprod p a b where
  Coprod :: {unCoprod :: p a b} -> Coprod p (COPR a) (COPR b)

instance (CategoryOf k) => Functor (COPR :: k -> COPROD k) where
  map = Coprod

instance (Profunctor p) => Profunctor (Coprod p) where
  dimap (Coprod l) (Coprod r) (Coprod p) = Coprod (dimap l r p)
  r \\ Coprod f = r \\ f
instance (Promonad p) => Promonad (Coprod p) where
  id = Coprod id
  Coprod f . Coprod g = Coprod (f . g)
instance (Representable p) => Representable (Coprod p) where
  type Coprod p % (COPR a) = COPR (p % a)
  index (Coprod p) = Coprod (index p)
  tabulate (Coprod f) = Coprod (tabulate f)
  repMap (Coprod f) = Coprod (repMap @p f)

instance
  (Profunctor f, Profunctor g, MonoidalProfunctor (Coprod f), MonoidalProfunctor (Coprod g))
  => MonoidalProfunctor (Coprod (f :.: g))
  where
  one = Coprod (nil :.: nil)
  Coprod (f :.: g) ** Coprod (h :.: i) = Coprod ((f ++ h) :.: (g ++ i))

-- | The same category as the category of @k@, but with coproducts as the tensor.
instance (CategoryOf k) => CategoryOf (COPROD k) where
  type (~>) = Coprod (~>)
  type Ob a = WrappedOb COPR a

instance (HasCoproducts k, cat ~ Hom k) => MonoidalProfunctor (Coprod cat :: COPROD k +-> COPROD k) where
  one = Coprod id
  Coprod f ** Coprod g = Coprod (f +++ g)

instance (HasCoproducts k) => MonoidalProfunctor (Coprod (Id :: k +-> k)) where
  one = Coprod (Id id)
  Coprod (Id f) ** Coprod (Id g) = Coprod (Id (f +++ g))

instance (HasCoproducts j, HasCoproducts k) => MonoidalProfunctor (Coprod (TerminalProfunctor :: j +-> k)) where
  one = Coprod TerminalProfunctor
  Coprod (TerminalProfunctor @a1 @b1) ** Coprod (TerminalProfunctor @a2 @b2) =
    withObCoprod @k @a1 @a2 $ withObCoprod @j @b1 @b2 $ Coprod TerminalProfunctor

nil :: (MonoidalProfunctor (Coprod p)) => p InitialObject InitialObject
nil = unCoprod one

(++) :: (MonoidalProfunctor (Coprod p)) => p a b -> p c d -> p (a || c) (b || d)
p ++ q = unCoprod (Coprod p ** Coprod q)

instance (HasInitialObject k) => HasInitialObject (COPROD k) where
  type InitialObject = COPR InitialObject
  initiate = Coprod initiate

instance (HasBinaryCoproducts k) => HasBinaryCoproducts (COPROD k) where
  type a || b = COPR (UN COPR a || UN COPR b)
  withObCoprod @(COPR a) @(COPR b) r = withObCoprod @k @a @b r
  lft @(COPR a) @(COPR b) = Coprod (lft @k @a @b)
  rgt @(COPR a) @(COPR b) = Coprod (rgt @k @a @b)
  Coprod f ||| Coprod g = Coprod (f ||| g)

instance (HasTerminalObject k) => HasTerminalObject (COPROD k) where
  type TerminalObject = COPR TerminalObject
  terminate = Coprod terminate

instance (HasBinaryProducts k) => HasBinaryProducts (COPROD k) where
  type COPR a && COPR b = COPR (a && b)
  withObProd @(COPR a) @(COPR b) r = withObProd @k @a @b r
  fst @(COPR a) @(COPR b) = Coprod (fst @k @a @b)
  snd @(COPR a) @(COPR b) = Coprod (snd @k @a @b)
  Coprod f &&& Coprod g = Coprod (f &&& g)

-- | Coproducts as monoidal tensor.
instance (HasCoproducts k) => Monoidal (COPROD k) where
  type Unit = COPR InitialObject
  type a ** b = COPR (UN COPR a || UN COPR b)
  withOb2 @(COPR a) @(COPR b) r = withObCoprod @k @a @b r
  leftUnitor = Coprod leftUnitorCoprod
  leftUnitorInv = Coprod leftUnitorCoprodInv
  rightUnitor = Coprod rightUnitorCoprod
  rightUnitorInv = Coprod rightUnitorCoprodInv
  associator @(COPR a) @(COPR b) @(COPR c) = Coprod (associatorCoprod @a @b @c)
  associatorInv @(COPR a) @(COPR b) @(COPR c) = Coprod (associatorCoprodInv @a @b @c)

leftUnitorCoprod :: forall {k} (a :: k). (HasCoproducts k, Ob a) => (InitialObject || a) ~> a
leftUnitorCoprod = initiate ||| id

leftUnitorCoprodInv :: forall {k} (a :: k). (HasCoproducts k, Ob a) => a ~> (InitialObject || a)
leftUnitorCoprodInv = rgt @k @InitialObject @a

rightUnitorCoprod :: forall {k} (a :: k). (HasCoproducts k, Ob a) => (a || InitialObject) ~> a
rightUnitorCoprod = id ||| initiate

rightUnitorCoprodInv :: forall {k} (a :: k). (HasCoproducts k, Ob a) => a ~> (a || InitialObject)
rightUnitorCoprodInv = lft @k @a @InitialObject

associatorCoprod :: forall {k} (a :: k) b c. (HasCoproducts k, Ob a, Ob b, Ob c) => (a || b) || c ~> a || (b || c)
associatorCoprod = (obj @a +++ lft @k @b @c) ||| withObCoprod @k @b @c (rgt @k @a @(b || c)) . rgt @k @b @c

associatorCoprodInv :: forall {k} (a :: k) b c. (HasCoproducts k, Ob a, Ob b, Ob c) => a || (b || c) ~> (a || b) || c
associatorCoprodInv = withObCoprod @k @a @b (lft @k @(a || b) @c) . lft @k @a @b ||| (rgt @k @a @b +++ obj @c)

instance (HasCoproducts k) => SymMonoidal (COPROD k) where
  swap @(COPR a) @(COPR b) = Coprod (swapCoprod @a @b)

-- | Inverse to 'Coprod': strips the 'COPR' wrappers from a profunctor between 'COPROD'-wrapped
-- kinds.
type Uncoprod :: (COPROD j +-> COPROD k) -> j +-> k
data Uncoprod p a b where
  Uncoprod :: p (COPR a) (COPR b) -> Uncoprod p a b

instance (Profunctor p, CategoryOf j, CategoryOf k) => Profunctor (Uncoprod p :: j +-> k) where
  dimap l r (Uncoprod p) = Uncoprod (dimap (Coprod l) (Coprod r) p \\ p)
  r \\ Uncoprod f = r \\ f

data family (+) (a :: k) (b :: k) :: k
instance (IsFreeOb (a :: FREE cs p), IsFreeOb b, HasBinaryCoproducts `Elem` cs) => IsFreeOb (a + b) where
  type Lower f (a + b) = Lower f a || Lower f b
  lowerOb @k' @f r =
    fromAll @HasBinaryCoproducts @cs @k'
      (withLowerOb @f @a (withLowerOb @f @b (withObCoprod @k' @(Lower f a) @(Lower f b) r)))
instance (HasBinaryCoproducts `Elem` cs) => HasStructure cs (p :: CAT k) HasBinaryCoproducts where
  data Struct HasBinaryCoproducts i o where
    Lft :: (Ob a, Ob b) => Struct HasBinaryCoproducts a (a + b)
    Rgt :: (Ob a, Ob b) => Struct HasBinaryCoproducts b (a + b)
    Sum :: a ~> o -> b ~> o -> Struct HasBinaryCoproducts (a + b) o
  foldStructure @f _ (Lft @a @b) = withLowerOb @f @a (withLowerOb @f @b (lft @_ @(Lower f a) @(Lower f b)))
  foldStructure @f _ (Rgt @a @b) = withLowerOb @f @a (withLowerOb @f @b (rgt @_ @(Lower f a) @(Lower f b)))
  foldStructure go (Sum g h) = go g ||| go h
instance (WithShow a) => Show (Struct HasBinaryCoproducts a b) where
  showsPrec _ Lft = P.showString "lft"
  showsPrec _ Rgt = P.showString "rgt"
  showsPrec d (Sum f g) =
    P.showParen (d P.> 4) P.$
      P.showsPrec 5 f . P.showString " ||| " . P.showsPrec 5 g
instance (HasBinaryCoproducts `Elem` cs) => HasBinaryCoproducts (FREE cs (p :: CAT k)) where
  type a || b = a + b
  withObCoprod r = r
  lft = F.St Lft F.Nil
  rgt = F.St Rgt F.Nil
  f ||| g = F.St (Sum f g) F.Nil \\ f \\ g

class ((a && b) ~ (a || b)) => CheckBiproduct a b
instance ((a && b) ~ (a || b)) => CheckBiproduct a b

class
  (HasBinaryCoproducts k, HasBinaryProducts k, forall (a :: k) (b :: k). (Ob a, Ob b) => CheckBiproduct a b) =>
  HasBiproducts k
  where
  sum :: (a :: k) ~> b -> a ~> b -> a ~> b
  sum f g = codiag . (f +++ g) . diag \\ f \\ g

instance (HasBinaryCoproducts k) => HasBinaryProducts (OPPOSITE k) where
  type a && b = OP (UN OP a || UN OP b)
  withObProd @(OP a) @(OP b) r = withObCoprod @k @a @b r
  fst @(OP a) @(OP b) = Op (lft @_ @a @b)
  snd @(OP a) @(OP b) = Op (rgt @_ @a @b)
  Op a &&& Op b = Op (a ||| b)

instance (HasBinaryProducts k) => HasBinaryCoproducts (OPPOSITE k) where
  type a || b = OP (UN OP a && UN OP b)
  withObCoprod @(OP a) @(OP b) r = withObProd @k @a @b r
  lft @(OP a) @(OP b) = Op (fst @_ @a @b)
  rgt @(OP a) @(OP b) = Op (snd @_ @a @b)
  Op a ||| Op b = Op (a &&& b)

-- | The left adjoint to the diagonal functor.
instance (HasBinaryCoproducts k) => Corepresentable (Rep Diag :: k +-> (k, k)) where
  type Rep Diag %% '(a, b) = a || b
  coindex (Rep (f :**: g)) = f ||| g
  corepUniv @'(a, b) = withObCoprod @k @a @b (Rep (lft @k @a @b :**: rgt @k @a @b))

-- | The universal property of the binary coproduct: the injections recover the components of
-- @f '|||' g@, and every arrow out of the coproduct is the copairing of its components.
instance Laws '[HasBinaryCoproducts] where
  laws =
    [ Law "lft" \ @a @b @c mor -> do
        f <- mor @a @c "f"
        g <- mor @b @c "g"
        f === (f ||| g) . lft @_ @a @b
    , Law "rgt" \ @a @b @c mor -> do
        f <- mor @a @c "f"
        g <- mor @b @c "g"
        g === (f ||| g) . rgt @_ @a @b
    , Law "copairing naturality" \ @a @b @c @d mor -> do
        f <- mor @a @c "f"
        g <- mor @b @c "g"
        h <- mor @c @d "h"
        (h . f) ||| (h . g) === h . (f ||| g)
    , Law "copairing the injections" \ @a @b _ ->
        withObCoprod @_ @a @b (lft @_ @a @b ||| rgt @_ @a @b === id)
    , Law "copairing uniqueness" \ @a @b @c mor -> withObCoprod @_ @a @b do
        p <- mor @(a || b) @c "p"
        p === (p . lft @_ @a @b) ||| (p . rgt @_ @a @b)
    ]