packages feed

proarrow-0.1.0.0: src/Proarrow/Category/Instance/Collage.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | The __collage__ (or cograph) of a profunctor @p@: a category on the disjoint union of @p@'s
-- two base categories ('L'- and 'R'-tagged objects, via the kind @'COLLAGE' p@), whose
-- cross-arrows @'L' a '~>' 'R' b@ are the elements @p a b@ (the 'L2R' constructor).
-- The injections 'InjL'\/'InjR' present a profunctor as a single category sitting over the
-- walking arrow 'Proarrow.Category.Instance.Bool.BOOL'.
module Proarrow.Category.Instance.Collage where

import Data.Kind (Constraint)
import Data.List (genericIndex)
import Data.Type.Nat (SNat (..), SNatI, snat, type Plus)
import Prelude (Maybe (..), map, type (~))

import Proarrow.Category.Enriched.Finitary (Finitary (..))
import Proarrow.Category.Enriched.Thin
  ( AtOb (..)
  , CodiscreteProfunctor
  , Decidable
  , DecidableProfunctor (..)
  , Decision (..)
  , DiscreteProfunctor (..)
  , Enumerable (..)
  , Finite (..)
  , FmapWrap
  , Indexed (..)
  , IndexedList (..)
  , KnownIndex
  , Length
  , Lookup
  , MapWrap
  , Thin
  , ThinProfunctor (..)
  , anyArr
  , mapDecision
  , withAtLookup
  , withWrapAtLookup
  )
import Proarrow.Category.Instance.Bool (BOOL (..), Booleans (..))
import Proarrow.Category.Instance.Coproduct qualified as C
import Proarrow.Category.Instance.Prof (Prof (..))
import Proarrow.Colimit.Initial (HasInitialObject (..), initiate')
import Proarrow.Core
  ( CAT
  , CategoryOf (..)
  , Hom
  , Kind
  , Obj
  , Profunctor (..)
  , Promonad (..)
  , dimapDefault
  , lmap
  , obj
  , rmap
  , type (+->)
  )
import Proarrow.Functor (FunctorForRep (..))
import Proarrow.Limit.Terminal (HasTerminalObject (..), terminate')
import Proarrow.Optic (iso)
import Proarrow.Optic.Iso (Iso')
import Proarrow.Profunctor.Instance.Direp (Direp (..))

type COLLAGE :: forall {j} {k}. k +-> j -> Kind
type data COLLAGE (p :: k +-> j) = L j | R k

type Collage :: CAT (COLLAGE p)
data Collage a b where
  InL :: a ~> b -> Collage (L a :: COLLAGE p) (L b :: COLLAGE p)
  InR :: a ~> b -> Collage (R a :: COLLAGE p) (R b :: COLLAGE p)
  L2R :: p a b -> Collage (L a :: COLLAGE p) (R b :: COLLAGE p)

type IsLR :: forall {p}. COLLAGE p -> Constraint
class IsLR (a :: COLLAGE p) where
  lrId :: Obj a
instance (Ob a, Promonad ((~>) :: CAT k)) => IsLR (L a :: (COLLAGE (p :: j +-> k))) where
  lrId = InL id
instance (Ob a, Promonad ((~>) :: CAT j)) => IsLR (R a :: (COLLAGE (p :: j +-> k))) where
  lrId = InR id

instance (Profunctor p) => Profunctor (Collage :: CAT (COLLAGE p)) where
  dimap = dimapDefault
  r \\ InL f = r \\ f
  r \\ InR f = r \\ f
  r \\ L2R p = r \\ p

instance (Profunctor p) => Promonad (Collage :: CAT (COLLAGE p)) where
  id = lrId
  InL g . InL f = InL (g . f)
  InR g . L2R p = L2R (rmap g p)
  L2R p . InL f = L2R (lmap f p)
  InR g . InR f = InR (g . f)

-- | The collage of a profunctor.
instance (Profunctor p) => CategoryOf (COLLAGE p) where
  type (~>) = Collage
  type Ob a = IsLR a

instance (HasInitialObject j, CategoryOf k, CodiscreteProfunctor p) => HasInitialObject (COLLAGE (p :: k +-> j)) where
  type InitialObject = L InitialObject
  initiate @a = case obj @a of
    InL a -> InL (initiate' a)
    InR b -> L2R anyArr \\ b

instance (HasTerminalObject k, CategoryOf j, CodiscreteProfunctor p) => HasTerminalObject (COLLAGE (p :: k +-> j)) where
  type TerminalObject = R TerminalObject
  terminate @a = case obj @a of
    InL a -> L2R anyArr \\ a
    InR b -> InR (terminate' b)

class HasArrowCollage p (a :: COLLAGE p) b where arrCoprod :: a ~> b
instance (Thin j, HasArrow (~>) (a :: j) b, Ob a, Ob b) => HasArrowCollage (p :: k +-> j) (L a) (L b) where
  arrCoprod = InL arr
instance (ThinProfunctor p, HasArrow p a b, Ob a, Ob b) => HasArrowCollage (p :: k +-> j) (L a) (R b) where
  arrCoprod = L2R arr
instance (Thin k, HasArrow (~>) (a :: k) b, Ob a, Ob b) => HasArrowCollage (p :: k +-> j) (R a) (R b) where
  arrCoprod = InR arr

instance (Thin j, Thin k, ThinProfunctor p) => ThinProfunctor (Collage :: CAT (COLLAGE (p :: k +-> j))) where
  type HasArrow (Collage :: CAT (COLLAGE p)) a b = HasArrowCollage p a b
  arr = arrCoprod
  withArr (InL f) r = withArr f r \\ f
  withArr (L2R p) r = withArr p r \\ p
  withArr (InR f) r = withArr f r \\ f

-- | Decided piecewise: within either side by that side's order, across by @p@, and never backwards.
instance
  (Decidable j, Decidable k, DecidableProfunctor p)
  => DecidableProfunctor (Collage :: CAT (COLLAGE (p :: k +-> j)))
  where
  type Holds (Collage :: CAT (COLLAGE (p :: k +-> j))) (L a) (L b) = Holds (Hom j) a b
  type Holds (Collage :: CAT (COLLAGE (p :: k +-> j))) (L a) (R b) = Holds p a b
  type Holds (Collage :: CAT (COLLAGE (p :: k +-> j))) (R a) (L b) = FLS
  type Holds (Collage :: CAT (COLLAGE (p :: k +-> j))) (R a) (R b) = Holds (Hom k) a b
  decide @x @y = case (obj @x, obj @y) of
    (InL @a f, InL @b g) -> mapDecision InL (decide @(Hom j) @a @b) \\ f \\ g
    (InL @a f, InR @b g) -> mapDecision L2R (decide @p @a @b) \\ f \\ g
    (InR _, InL _) -> No
    (InR @a f, InR @b g) -> mapDecision InR (decide @(Hom k) @a @b) \\ f \\ g
  toHolds (InL f) r = toHolds f r
  toHolds (L2R p) r = toHolds p r
  toHolds (InR f) r = toHolds f r

data family InjL :: forall (p :: k +-> j) -> j +-> COLLAGE p
instance (Profunctor p) => FunctorForRep (InjL p) where
  type InjL p @ a = L a
  fmap = InL

data family InjR :: forall (p :: k +-> j) -> k +-> COLLAGE p
instance (Profunctor p) => FunctorForRep (InjR p) where
  type InjR p @ a = R a
  fmap = InR

collageUniv :: forall {j} {k} (p :: k +-> j). (Profunctor p) => Iso' p (Direp (InjL p) (InjR p))
collageUniv = iso (Prof \p -> Direp (L2R p) \\ p) (Prof \case Direp (L2R q) -> q)

data family CollageAsCoprod :: COLLAGE (p :: k +-> j) +-> C.COPRODUCT j k
instance (DiscreteProfunctor p) => FunctorForRep (CollageAsCoprod :: COLLAGE (p :: k +-> j) +-> C.COPRODUCT j k) where
  type CollageAsCoprod @ L a = C.L a
  type CollageAsCoprod @ R a = C.R a
  fmap (InL f) = C.InjL f
  fmap (InR f) = C.InjR f
  fmap (L2R p) = exfalso p

data family ProjTo2 :: forall (p :: k +-> j) -> COLLAGE p +-> BOOL
instance (Profunctor p) => FunctorForRep (ProjTo2 p) where
  type ProjTo2 p @ L a = FLS
  type ProjTo2 p @ R a = TRU
  fmap = \case
    InL _ -> Fls
    InR _ -> Tru
    L2R _ -> F2T

-- * Numbering the collage

-- | The collage numbers the left category's objects first and the right category's after them.
type CollageObjects :: forall {j} {k}. forall (p :: k +-> j) -> [j] -> [COLLAGE p]
type family CollageObjects p xs where
  CollageObjects (p :: k +-> j) '[] = MapWrap R (Objects k)
  CollageObjects p (x ': xs) = L x ': CollageObjects p xs

instance (Finite j, Finite k) => Indexed (COLLAGE (p :: k +-> j)) where
  type Index (L a) = Index a
  type Index (R b :: COLLAGE (p :: k +-> j)) = Plus (Length (Objects j)) (Index b)

-- | An object of the left category is an object of the collage, keeping its index; one of the right
-- category is too, shifted past all the left ones. Both walk the left object list, and both hand the
-- fact to a continuation, since at each step the statement about the tail is the statement about the
-- whole list already reduced.
withCollageL
  :: forall {j} {k} (p :: k +-> j) (x :: j) r
   . (Finite j, Finite k, KnownIndex x)
  => ((KnownIndex (L x :: COLLAGE p)) => r) -> r
withCollageL r = withAtLookup @j (snat @(Index x)) (go (finite @j) (snat @(Index x)) r)
  where
    go
      :: forall xs i
       . (Lookup xs i ~ 'Just x)
      => IndexedList xs -> SNat i -> ((Lookup (CollageObjects p xs) i ~ 'Just (L x)) => r) -> r
    go (FCons _) SZ k = k
    go (FCons xs) (SS @i') k = go xs (snat @i') k

withCollageR
  :: forall {j} {k} (p :: k +-> j) (y :: k) r
   . (Finite j, Finite k, KnownIndex y)
  => ((KnownIndex (R y :: COLLAGE p)) => r) -> r
withCollageR r = go (finite @j) r
  where
    go
      :: forall xs
       . IndexedList xs
      -> ( ( SNatI (Plus (Length xs) (Index y))
           , Lookup (CollageObjects p xs) (Plus (Length xs) (Index y)) ~ FmapWrap R (At k (Index y))
           )
           => r
         )
      -> r
    go FNil k = withWrapAtLookup @(R :: k -> COLLAGE p) (snat @(Index y)) k
    go (FCons xs) k = go xs k

instance (Finite j, Finite k) => Finite (COLLAGE (p :: k +-> j)) where
  type Objects (COLLAGE (p :: k +-> j)) = CollageObjects p (Objects j)
  finite = goL (finite @j)
    where
      goL :: forall xs. IndexedList xs -> IndexedList (CollageObjects p xs)
      goL FNil = goR (finite @k)
      goL (FCons @x xs) = withCollageL @p @x (FCons @(L x) (goL xs))
      goR :: forall ys. IndexedList ys -> IndexedList (MapWrap (R :: k -> COLLAGE p) ys)
      goR FNil = FNil
      goR (FCons @y ys) = withCollageR @p @y (FCons @(R y) (goR ys))

-- | The collage of a finitary profunctor between finite categories is a finite category: a
-- hom-set is a base hom-set, an element set of @p@ for a cross-arrow, or empty going back.
--
-- This is the cheapest source of a finite category that is /not a poset/: elements of @p@
-- between one pair of objects are parallel arrows.
instance
  (Finitary (Hom j), Finitary (Hom k), Finitary p)
  => Finitary (Collage :: CAT (COLLAGE (p :: k +-> j)))
  where
  size @a @b = case (obj @a, obj @b) of
    (InL @x f, InL @y g) -> size @(Hom j) @x @y \\ f \\ g
    (InL @x f, InR @y g) -> size @p @x @y \\ f \\ g
    (InR _, InL _) -> 0
    (InR @x f, InR @y g) -> size @(Hom k) @x @y \\ f \\ g
  toIndex = \case
    InL f -> toIndex f \\ f
    InR f -> toIndex f \\ f
    L2R x -> toIndex x \\ x
  fromIndex @a @b = genericIndex (elements @(Collage :: CAT (COLLAGE p)) @a @b)
  elements @a @b = case (obj @a, obj @b) of
    (InL @x f, InL @y g) -> map InL (elements @(Hom j) @x @y) \\ f \\ g
    (InL @x f, InR @y g) -> map L2R (elements @p @x @y) \\ f \\ g
    (InR _, InL _) -> []
    (InR @x f, InR @y g) -> map InR (elements @(Hom k) @x @y) \\ f \\ g

instance (Enumerable j, Enumerable k, Profunctor p) => Enumerable (COLLAGE (p :: k +-> j)) where
  withIndex @a r = case obj @a of
    InL @x f -> withIndex @j @x (withCollageL @p @x r) \\ f
    InR @y f -> withIndex @k @y (withCollageR @p @y r) \\ f
  atOb = go (finite @j)
    where
      go :: forall xs i. IndexedList xs -> SNat i -> AtOb (COLLAGE p) (Lookup (CollageObjects p xs) i)
      go FNil i = withWrapAtLookup @(R :: k -> COLLAGE p) i case atOb @k i of
        AtJust @_ @y -> withCollageR @p @y AtJust
        AtNothing -> AtNothing
      go (FCons @x _) SZ = withOb @j @x (withCollageL @p @x AtJust)
      go (FCons xs) (SS @i') = go xs (snat @i')