proarrow-0.1.0.0: src/Proarrow/Category/Enriched.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
-- | Categories and profunctors enriched in a monoidal category @v@, encoded via their underlying
-- ordinary category\/profunctor: 'EnrichedProfunctor' equips a regular profunctor with hom-objects
-- @'ProObj' v p a b@ in @v@ from which the enriched structure is recovered, and a category is
-- 'Enriched' when its hom-profunctor is. Instances include the self-enrichment of a
-- 'Proarrow.Category.Monoidal.Closed.Closed' category.
module Proarrow.Category.Enriched where
import Data.Kind (Constraint, Type)
import Proarrow.Category.Enriched.Dagger (DaggerProfunctor (..))
import Proarrow.Category.Enriched.Finitary (Elt (..), Finitary, LocallyFinite)
import Proarrow.Category.Enriched.Thin
( CodiscreteProfunctor (..)
, Decidable
, DecidableProfunctor (..)
, Decision (..)
, Thin
, ThinProfunctor (..)
)
import Proarrow.Category.Instance.Bool (BOOL (..), Booleans (..))
import Proarrow.Category.Instance.Constraint (CONSTRAINT (..), (:-) (..))
import Proarrow.Category.Instance.FinHask (FINHASK (..))
import Proarrow.Category.Instance.FinHask qualified as F
import Proarrow.Category.Instance.Monoid (MONOID (..), Mon (..))
import Proarrow.Category.Instance.Opposite (OPPOSITE (..), Op (..))
import Proarrow.Category.Instance.Product ((:**:) (..))
import Proarrow.Category.Instance.Prof (Prof)
import Proarrow.Category.Instance.Sub (SUBCAT (..), Sub (..))
import Proarrow.Category.Instance.Unit qualified as U
import Proarrow.Category.Monoidal (Monoidal (..), SymMonoidal (..), leftUnitorInvWith, rightUnitorInvWith)
import Proarrow.Category.Monoidal.Closed qualified as E
import Proarrow.Core (Any, CAT, CategoryOf (..), Hom, Kind, Profunctor ((\\)), Promonad (..), type (+->))
import Proarrow.Core qualified as P
import Proarrow.Limit.BinaryProduct (PROD, Prod)
import Proarrow.Monoid (Monoid (..))
import Proarrow.Profunctor.Instance.Exponential ()
-- | Working with enriched categories and profunctors in Haskell is hard.
-- Instead we encode them using the underlying regular category/profunctor,
-- and show that the enriched structure can be recovered.
--
-- Call an arrow @'Unit' '~>' x@ an /element/ of @x@. The laws say that the elements of
-- @'ProObj' v p a b@ are those of the hom-set @p a b@, and that its two actions are the
-- profunctor's:
--
-- [Elements] 'underlying' and 'enriched' are mutually inverse, so 'underlying' is a bijection from
-- @p a b@ onto the elements of @'ProObj' v p a b@.
--
-- [Right action] for @f :: b '~>' c@ in @j@ and @x :: p a b@, where @'underlying' f@ names @f@ as
-- an element of @'HomObj' v b c@:
--
-- > rmap . (underlying f ** underlying x) . leftUnitorInv == underlying (P.rmap f x)
--
-- [Left action] dually, for @g :: c '~>' a@ in @k@:
--
-- > lmap . (underlying g ** underlying x) . leftUnitorInv == underlying (P.lmap g x)
--
-- These fix 'rmap' and 'lmap' completely iff @v@ is well-pointed, as 'Type' and the thin @v@s are.
-- Functoriality follows from the 'Profunctor' instance. At @p ~ 'Hom' k@ the right-action law says
-- that the enriched composition 'comp' is @('.')@.
type EnrichedProfunctor :: forall {j} {k}. Kind -> j +-> k -> Constraint
class (Monoidal v, Profunctor p, Enriched v j, Enriched v k) => EnrichedProfunctor v (p :: j +-> k) where
type ProObj v (p :: j +-> k) (a :: k) (b :: j) :: v
withProObj :: (Ob (a :: k), Ob b) => ((Ob (ProObj v p a b)) => r) -> r
underlying :: p a b -> Unit ~> ProObj v p a b
enriched :: (Ob a, Ob b) => Unit ~> ProObj v p a b -> p a b
rmap :: (Ob a, Ob b, Ob c) => HomObj v b c ** ProObj v p a b ~> ProObj v p a c
lmap :: (Ob a, Ob b, Ob c) => HomObj v c a ** ProObj v p a b ~> ProObj v p c b
class (EnrichedProfunctor v (Hom k)) => Enriched v k
instance (EnrichedProfunctor v (Hom k)) => Enriched v k
type HomObj v (a :: k) (b :: k) = ProObj v (Hom k) a b
comp :: forall {k} v (a :: k) b c. (Enriched v k, Ob a, Ob b, Ob c) => HomObj v b c ** HomObj v a b ~> HomObj v a c
comp = rmap @v @(Hom k) @a @b @c
-- | Closed monoidal categories are enriched in themselves.
type HomSelf a b = a E.~~> b
underlyingSelf :: (E.Closed k) => (a :: k) ~> b -> Unit ~> HomSelf a b
underlyingSelf = E.mkExponential
enrichedSelf :: (E.Closed k, Ob (a :: k), Ob b) => Unit ~> HomSelf a b -> a ~> b
enrichedSelf = E.lower
compSelf :: forall {k} (a :: k) b c. (E.Closed k, Ob a, Ob b, Ob c) => HomSelf b c ** HomSelf a b ~> HomSelf a c
compSelf = E.comp @a @b @c
-- abusing SUBCAT Any as a cheap wrapper to prevent overlapping instances
type Clone k = SUBCAT (Any :: k -> Constraint)
-- | A monoid is a one object enriched category.
instance (Monoid (m :: k)) => EnrichedProfunctor (Clone k) (Mon :: CAT (MONOID (m :: k))) where
type ProObj (Clone k) (Mon :: CAT (MONOID m)) M M = SUB m
withProObj r = r
underlying (Mon f) = Sub f
enriched (Sub f) = Mon f
rmap = Sub mappend
lmap = Sub mappend
instance (Profunctor p) => EnrichedProfunctor Type p where
type ProObj Type p a b = p a b
withProObj r = r
underlying p () = p
enriched f = f ()
rmap = E.uncurry P.rmap
lmap = E.uncurry P.lmap
instance (DaggerProfunctor p) => EnrichedProfunctor (Type, Type) p where
type ProObj (Type, Type) p a b = '(p a b, p b a)
withProObj r = r
underlying p = (\() -> p) :**: (\() -> dagger p)
enriched (f :**: _) = f ()
rmap = E.uncurry P.rmap :**: E.uncurry P.lmap
lmap = E.uncurry P.lmap :**: E.uncurry P.rmap
instance (ThinProfunctor p, Thin j, Thin k) => EnrichedProfunctor CONSTRAINT (p :: j +-> k) where
type ProObj CONSTRAINT p a b = CNSTRNT (HasArrow p a b)
withProObj r = r
underlying p = Entails \r -> withArr p r
enriched (Entails f) = f arr
rmap @a @b @c = Entails \r -> withArr @p (P.rmap (arr @(~>) @b @c) (arr @p @a @b)) r
lmap @a @b @c = Entails \r -> withArr @p (P.lmap (arr @(~>) @c @a) (arr @p @a @b)) r
-- | A decidable thin profunctor is a profunctor enriched in the walking arrow: its hom-object is the
-- type-level 'Holds', an element of it is an arrow, and composition is conjunction.
instance (DecidableProfunctor p, Decidable j, Decidable k) => EnrichedProfunctor BOOL (p :: j +-> k) where
type ProObj BOOL p a b = Holds p a b
withProObj @a @b r = case decide @p @a @b of
Yes _ -> r
No -> r
underlying p = toHolds p Tru
enriched @a @b f = case decide @p @a @b of
Yes x -> x
No -> case f of {}
rmap @a @b @c = case (decide @(Hom j) @b @c, decide @p @a @b) of
(Yes g, Yes x) -> toHolds (P.rmap g x) Tru
(No, _) -> fromFls (decide @p @a @c)
(Yes _, No) -> fromFls (decide @p @a @c)
lmap @a @b @c = case (decide @(Hom k) @c @a, decide @p @a @b) of
(Yes g, Yes x) -> toHolds (P.lmap g x) Tru
(No, _) -> fromFls (decide @p @c @b)
(Yes _, No) -> fromFls (decide @p @c @b)
-- | @FLS@ is initial, and a decision tells us which object we are aiming at.
fromFls :: Decision p a b h -> Booleans FLS h
fromFls (Yes _) = F2T
fromFls No = Fls
-- | __A finitary profunctor is a profunctor enriched in finite sets.__ The hom-object is the
-- hom-set itself, which 'Elt' makes an object of 'FINHASK' out of nothing but the numbering, and the
-- whiskerings are the two 'dimap's.
instance (Finitary p, LocallyFinite j, LocallyFinite k) => EnrichedProfunctor FINHASK (p :: j +-> k) where
type ProObj FINHASK p a b = FH (Elt p a b)
withProObj r = r
underlying x = F.arr (\() -> Elt x) \\ x
enriched f = unElt (f F.! ())
rmap @a = F.arr \(Elt g, Elt x) -> Elt (P.dimap (id @_ @a) g x)
lmap @_ @b = F.arr \(Elt g, Elt x) -> Elt (P.dimap g (id @_ @b) x)
-- | The category of profunctors is enriched in itself: the hom-object is the internal hom
-- @p ':~>:' q@, an element of it is a natural transformation, and composition is the internal one.
-- Cartesian closed, hence the 'PROD' wrapper (@j '+->' k@\'s own tensor is Day convolution).
--
-- This self-enrichment is written the generic way, from 'HomSelf' and friends. Those apply to any
-- 'Closed' 'SymMonoidal' kind that has no enrichment instance of its own covering its
-- hom-profunctor.
instance (CategoryOf j, CategoryOf k) => EnrichedProfunctor (PROD (j +-> k)) (Prod (Prof :: CAT (j +-> k))) where
type ProObj (PROD (j +-> k)) (Prod (Prof :: CAT (j +-> k))) p q = HomSelf p q
withProObj r = r
underlying = underlyingSelf
enriched = enrichedSelf
rmap = compSelf
lmap = compSelf . swap
instance (CodiscreteProfunctor p) => EnrichedProfunctor () p where
type ProObj () p a b = '()
withProObj r = r
underlying _ = U.Unit
enriched U.Unit = anyArr
rmap = U.Unit
lmap = U.Unit
instance (EnrichedProfunctor v p) => EnrichedProfunctor (Clone v) (Op p) where
type ProObj (Clone v) (Op p) (OP a) (OP b) = SUB (ProObj v p b a)
withProObj @(OP a) @(OP b) r = withProObj @v @p @b @a r
underlying (Op f) = Sub (underlying @v @p f)
enriched (Sub f) = Op (enriched f)
rmap @(OP a) @(OP b) @(OP c) = Sub (lmap @v @p @b @a @c)
lmap @(OP a) @(OP b) @(OP c) = Sub (rmap @v @p @b @a @c)
-- | A generalized arrow of an enriched category. If @k@ is both powered and copowered, this is an adjunction.
type GenArrow :: OPPOSITE v -> k +-> k
data GenArrow n a b where
GenArrow :: (Ob a, Ob b) => n ~> HomObj v a b -> GenArrow (OP n) a b
instance (Ob (n :: v), Enriched v k, CategoryOf k) => Profunctor (GenArrow (OP n) :: k +-> k) where
dimap @c @a @b @d l r (GenArrow f) =
GenArrow
( let g = comp @v @c @a @b . rightUnitorInvWith (underlying @v l) . f
in comp @v @c @b @d . leftUnitorInvWith (underlying @v r) . g \\ g
)
\\ f
\\ l
\\ r
r \\ GenArrow f = r \\ f