proarrow-0.1.0.0: src/Proarrow/Category/Monoidal/Cartesian.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Cartesian monoidal categories ('Cartesian': tensor = product, with 'CopyDiscard' as
-- superclass by Fox's theorem), cartesian closed ones ('CCC') and bicartesian closed ones
-- ('BiCCC', which implies 'Distributive'): the meeting point of the monoidal and the product
-- worlds, which never import each other. Lives above
-- "Proarrow.Category.Monoidal.CopyDiscard" rather than with the products, because the superclass
-- points that way.
module Proarrow.Category.Monoidal.Cartesian where
import Prelude (($), type (~))
import Prelude qualified as P
import Proarrow.Category.Instance.Free (Elems, FREE, Free (..), HasStructure (..), Lower, withLowerOb)
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal, UnitF, type (**!))
import Proarrow.Category.Monoidal.Closed (Closed (..), uncurry)
import Proarrow.Category.Monoidal.CopyDiscard (CopyDiscard)
import Proarrow.Category.Monoidal.Distributive (Distributive (..), Traversable (..))
import Proarrow.Colimit.BinaryCoproduct (HasBinaryCoproducts (..), type (||))
import Proarrow.Core (CAT, CategoryOf (..), Profunctor (..), Promonad (..), lmap, type (+->))
import Proarrow.Limit.BinaryProduct
( HasBinaryProducts (..)
, HasProducts
, PROD (..)
, Prod (..)
, diag
, swapProd
, type (*!)
)
import Proarrow.Limit.Terminal (HasTerminalObject (..), Semicartesian, TermF)
import Proarrow.Monoid (CocommutativeComonoid, Comonoid (..))
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Instance.Product ((:*:) (..))
import Proarrow.Profunctor.Representable (RepCostar (..), Representable (..), withObRep)
class (a ** b ~ a && b) => TensorIsProduct a b
instance (a ** b ~ a && b) => TensorIsProduct a b
-- | A cartesian monoidal category: the tensor is the product and the unit the terminal object.
-- By Fox's theorem this is the same as a 'CopyDiscard' category whose 'copy' and 'discard' are
-- natural, so 'CopyDiscard' is a superclass. Every cartesian category supplies its diagonals as
-- comonoids, and anything asking only for copying and discarding (prisms, for instance) accepts
-- a cartesian category directly. The law relating the two is @copy = id &&& id@ and
-- @discard = terminate@.
class
(HasProducts k, SymMonoidal k, Semicartesian k, CopyDiscard k, forall (a :: k) (b :: k). TensorIsProduct a b) =>
Cartesian k
instance
(HasProducts k, SymMonoidal k, Semicartesian k, CopyDiscard k, forall (a :: k) (b :: k). TensorIsProduct a b)
=> Cartesian k
-- | In a category with products every object is a comonoid via the diagonal and the terminal
-- map. With this comonoid structure 'PROD' is 'CopyDiscard' and 'Cartesian'.
instance (HasProducts k, Ob a) => Comonoid (PR (a :: k)) where
counit = Prod terminate
comult = Prod diag
instance (HasProducts k, Ob a) => CocommutativeComonoid (PR (a :: k))
-- | A category with products, viewed through 'PROD' as a monoidal category, is cartesian.
instance (HasProducts k) => CopyDiscard (PROD k)
-- | In a cartesian category the tensor /is/ the product ('TensorIsProduct'), but GHC only applies
-- that equation at the top of a type, never under another type family such as @('||')@, and
-- using the quantified form of it directly sends the solver in circles. These two identities take
-- the equation as an ordinary given (discharged at the call site from the quantified superclass
-- of 'Cartesian'), so it can be applied where a product-typed leg meets tensor-typed plumbing.
tensorToProduct :: forall {k} (a :: k) b. (HasBinaryProducts k, TensorIsProduct a b, Ob a, Ob b) => (a ** b) ~> (a && b)
tensorToProduct = withObProd @k @a @b id
productToTensor :: forall {k} (a :: k) b. (HasBinaryProducts k, TensorIsProduct a b, Ob a, Ob b) => (a && b) ~> (a ** b)
productToTensor = withObProd @k @a @b id
-- | Every functor between cartesian categories is oplax monoidal, @f (a && b) ~> f a && f b@ by the
-- projections and @f Unit ~> Unit@ by terminality. On the 'RepCostar' of its representable profunctor
-- this is 'Proarrow.Category.Monoidal.OplaxMonoidal'.
instance (Representable p, Cartesian j, Cartesian k) => MonoidalProfunctor (RepCostar (p :: j +-> k)) where
one = withObRep @p @Unit (RepCostar terminate)
RepCostar @a f ** RepCostar @b g = withOb2 @j @a @b (RepCostar (unparRepCartesian @p @a @b f g))
unparRepCartesian
:: forall {j} {k} p (a :: j) b a' b'
. ( Representable (p :: j +-> k)
, Cartesian k
, Cartesian j
, TensorIsProduct a b
, TensorIsProduct a' b'
, Ob a
, Ob b
)
=> (p % a ~> a') -> (p % b ~> b') -> p % (a ** b) ~> (a' ** b')
unparRepCartesian f g = f . repMap @p (fst @j @a @b) &&& g . repMap @p (snd @j @a @b)
class (Cartesian k, Closed k) => CCC k
instance (Cartesian k, Closed k) => CCC k
type Bicartesian k = (Cartesian k, Distributive k)
-- | Bicartesian closed: cartesian closed with coproducts. Every such category is distributive
-- (@a &&@ is a left adjoint, so it preserves coproducts), and the class says so, so that
-- 'Distributive' never has to be asked for separately.
class (CCC k, Distributive k) => BiCCC k
instance (CCC k, Distributive k) => BiCCC k
-- | Distributivity of the /product/ over coproducts, derived from closedness: in any BiCCC the
-- functor @a &&@ is a left adjoint and so preserves coproducts.
distLProd :: forall {k} (a :: k) (b :: k) (c :: k). (BiCCC k, Ob a, Ob b, Ob c) => (a && (b || c)) ~> (a && b || a && c)
distLProd = (swapProd @b @a +++ swapProd @c @a) . distRProd @b @c @a . withObCoprod @k @b @c (swapProd @a @(b || c))
distRProd :: forall {k} (a :: k) (b :: k) (c :: k). (BiCCC k, Ob a, Ob b, Ob c) => ((a || b) && c) ~> (a && c || b && c)
distRProd =
withObProd @k @a @c $
withObProd @k @b @c $
withObCoprod @k @(a && c) @(b && c) $
uncurry @c (curry @k @a @c (lft @k @(a && c) @(b && c)) ||| curry @k @b @c (rgt @k @(a && c) @(b && c)))
instance (BiCCC k) => Distributive (PROD k) where
distL @(PR a) @(PR b) @(PR c) = Prod (distLProd @a @b @c)
distR @(PR a) @(PR b) @(PR c) = Prod (distRProd @a @b @c)
absorbL @(PR a) = Prod (snd @k @a)
absorbR @(PR a) = Prod (fst @k @_ @a)
instance (Cartesian k, Traversable p, Traversable q) => Traversable ((p :: k +-> k) :*: q) where
traverse ((p :*: q) :.: r) = case (traverse (p :.: r), traverse (q :.: r)) of
((:.:) @a r' p', (:.:) @b r'' q') -> lmap diag (r' ** r'') :.: (lmap (fst @k @a @b) p' :*: lmap (snd @k @a @b) q') \\ p \\ p' \\ q'
ap
:: forall {j} {k} y a x p
. (Closed j, Cartesian k, MonoidalProfunctor (p :: j +-> k), Ob y)
=> p a (x ~~> y)
-> p a x
-> p a y
ap pf px = dimap diag (apply @j @x @y) (pf ** px) \\ px
-- | The free-category structure for 'Cartesian'. The free category cannot satisfy the /type
-- equality/ @tensor = product@ ('TensorIsProduct' fails on it, see "Proarrow.Category.Instance.Free"),
-- but it can carry the corresponding isomorphisms as formal arrows, interpreted to the identity in
-- any cartesian target ('productToTensor' and friends). So a free category can serve as syntax
-- for cartesian (closed) categories without collapsing its object grammar.
instance
('[Cartesian, HasTerminalObject, HasBinaryProducts, Monoidal] `Elems` cs)
=> HasStructure cs (p :: CAT k) Cartesian
where
data Struct Cartesian i o where
ProdToTensor :: (Ob a, Ob b) => Struct Cartesian (a *! b) (a **! b)
TensorToProd :: (Ob a, Ob b) => Struct Cartesian (a **! b) (a *! b)
TermToUnit :: Struct Cartesian TermF UnitF
UnitToTerm :: Struct Cartesian UnitF TermF
foldStructure @f _ (ProdToTensor @a @b) =
withLowerOb @f @a (withLowerOb @f @b (productToTensor @(Lower f a) @(Lower f b)))
foldStructure @f _ (TensorToProd @a @b) =
withLowerOb @f @a (withLowerOb @f @b (tensorToProduct @(Lower f a) @(Lower f b)))
foldStructure _ TermToUnit = id
foldStructure _ UnitToTerm = id
instance P.Show (Struct Cartesian a b) where
showsPrec _ ProdToTensor = P.showString "prodToTensor"
showsPrec _ TensorToProd = P.showString "tensorToProd"
showsPrec _ TermToUnit = P.showString "termToUnit"
showsPrec _ UnitToTerm = P.showString "unitToTerm"
-- | The formal @tensor = product@ isomorphisms of a free category with 'Cartesian' in its list.
prodToTensor
:: forall {k} {cs} {p :: CAT k} (a :: FREE cs p) b
. ('[Cartesian, HasTerminalObject, HasBinaryProducts, Monoidal] `Elems` cs, Ob a, Ob b)
=> (a *! b) ~> (a **! b)
prodToTensor = St ProdToTensor Nil
tensorToProd
:: forall {k} {cs} {p :: CAT k} (a :: FREE cs p) b
. ('[Cartesian, HasTerminalObject, HasBinaryProducts, Monoidal] `Elems` cs, Ob a, Ob b)
=> (a **! b) ~> (a *! b)
tensorToProd = St TensorToProd Nil
termToUnit
:: forall {k} {cs} {p :: CAT k}
. ('[Cartesian, HasTerminalObject, HasBinaryProducts, Monoidal] `Elems` cs)
=> (TermF :: FREE cs p) ~> UnitF
termToUnit = St TermToUnit Nil
unitToTerm
:: forall {k} {cs} {p :: CAT k}
. ('[Cartesian, HasTerminalObject, HasBinaryProducts, Monoidal] `Elems` cs)
=> (UnitF :: FREE cs p) ~> TermF
unitToTerm = St UnitToTerm Nil