proarrow-0.1.0.0: src/Proarrow/Category/Monoidal/Distributive.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Distributivity of a tensor over coproducts: a 'Distributive' category has 'distL'\/'distR' and
-- absorption by the initial object, and a 'DistributiveProfunctor' is monoidal for both tensor and
-- coproduct. Also home to 'Traversable' and 'Cotraversable' profunctors, which distribute any
-- 'StrongDistributiveProfunctor' and underlie 'Proarrow.Optic.Traversal.Traversal'.
module Proarrow.Category.Monoidal.Distributive where
import Data.Bifunctor (bimap)
import Data.Kind (Constraint, Type)
import Prelude qualified as P
import Proarrow.Category.Instance.Bool (BOOL (..), Booleans (..))
import Proarrow.Category.Instance.Free (Elems, FREE, Free (..), HasStructure (..), Lower, withLowerOb)
import Proarrow.Category.Instance.Product ((:**:) (..))
import Proarrow.Category.Instance.Unit qualified as U
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..), first, second, type (**!))
import Proarrow.Category.Monoidal.Action (CoprodAction)
import Proarrow.Category.Monoidal.Closed (Closed (..), uncurry)
import Proarrow.Category.Monoidal.CopyDiscard (CopyDiscard (..))
import Proarrow.Category.Monoidal.Strength (MonStrong, Strong (..))
import Proarrow.Colimit.BinaryCoproduct
( COPROD (..)
, Coprod (..)
, HasBinaryCoproducts (..)
, HasCoproducts
, codiag
, (++)
, type (+)
)
import Proarrow.Colimit.Initial (HasInitialObject (..), InitF)
import Proarrow.Core (CAT, CategoryOf (..), Kind, Profunctor (..), Promonad (..), lmap, obj, (//), (:~>), type (+->))
import Proarrow.Monoid (Monoid (..))
import Proarrow.Profunctor.Corepresentable (Corepresentable (..), coindex, corepUniv)
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Instance.Constant (Constant)
import Proarrow.Profunctor.Instance.Coproduct ((:+:) (..))
import Proarrow.Profunctor.Instance.Identity (Id (..))
import Proarrow.Profunctor.Instance.Product ((:*:) (..))
import Proarrow.Profunctor.Representable (Rep (..), RepCostar (..), Representable (..), repUniv)
import Proarrow.Tools.Laws (Inverses (..), Labelled (..), Laws (..), inverses)
import Prelude (($))
class (MonoidalProfunctor p, MonoidalProfunctor (Coprod p)) => DistributiveProfunctor p
instance (MonoidalProfunctor p, MonoidalProfunctor (Coprod p)) => DistributiveProfunctor p
-- | A distributive monoidal category: the tensor distributes over coproducts, and annihilates the
-- 'InitialObject'. The monoidal and coproduct worlds meet here.
--
-- __Laws:__
--
-- Each of the four arrows is invertible, with the named inverse:
--
-- * @'distL'@ is inverse to 'distLInv', and @'distR'@ to 'distRInv'
-- * @'absorbL'@ and @'absorbR'@ are inverse to 'Proarrow.Colimit.Initial.initiate'
--
-- Checked by 'Proarrow.Testing.Laws.testDistributive'.
class (Monoidal k, HasCoproducts k) => Distributive k where
-- | Distributes a tensor on the left over a coproduct.
distL :: (Ob (a :: k), Ob b, Ob c) => (a ** (b || c)) ~> (a ** b || a ** c)
-- | Distributes a tensor on the right over a coproduct.
distR :: (Ob (a :: k), Ob b, Ob c) => ((a || b) ** c) ~> (a ** c || b ** c)
-- | The 'InitialObject' annihilates the tensor on the right.
absorbL :: (Ob (a :: k)) => (a ** InitialObject) ~> InitialObject
-- | The 'InitialObject' annihilates the tensor on the left.
absorbR :: (Ob (a :: k)) => (InitialObject ** a) ~> InitialObject
-- | The structures the free category needs for 'Distributive', and those its laws are stated for.
type DistributiveStructures :: [Kind -> Constraint]
type DistributiveStructures = '[Monoidal, HasInitialObject, HasBinaryCoproducts, Distributive]
-- | The free-category structure for 'Distributive': formal distributors and absorbers,
-- interpreted by 'foldStructure' through the target's own. Together with the coproduct and
-- monoidal structures this makes a free category over a bare quiver distributive without asking
-- anything of the quiver's category.
instance
(DistributiveStructures `Elems` cs)
=> HasStructure cs (p :: CAT k) Distributive
where
data Struct Distributive i o where
DistL :: (Ob a, Ob b, Ob c) => Struct Distributive (a **! (b + c)) ((a **! b) + (a **! c))
DistR :: (Ob a, Ob b, Ob c) => Struct Distributive ((a + b) **! c) ((a **! c) + (b **! c))
AbsorbL :: (Ob a) => Struct Distributive (a **! InitF) InitF
AbsorbR :: (Ob a) => Struct Distributive (InitF **! a) InitF
foldStructure @f _ (DistL @a @b @c) =
withLowerOb @f @a (withLowerOb @f @b (withLowerOb @f @c (distL @_ @(Lower f a) @(Lower f b) @(Lower f c))))
foldStructure @f _ (DistR @a @b @c) =
withLowerOb @f @a (withLowerOb @f @b (withLowerOb @f @c (distR @_ @(Lower f a) @(Lower f b) @(Lower f c))))
foldStructure @f _ (AbsorbL @a) = withLowerOb @f @a (absorbL @_ @(Lower f a))
foldStructure @f _ (AbsorbR @a) = withLowerOb @f @a (absorbR @_ @(Lower f a))
instance P.Show (Struct Distributive a b) where
showsPrec _ DistL = P.showString "distL"
showsPrec _ DistR = P.showString "distR"
showsPrec _ AbsorbL = P.showString "absorbL"
showsPrec _ AbsorbR = P.showString "absorbR"
instance (DistributiveStructures `Elems` cs) => Distributive (FREE cs (p :: CAT k)) where
distL = St DistL Nil
distR = St DistR Nil
absorbL = St AbsorbL Nil
absorbR = St AbsorbR Nil
distLInv
:: forall {k} a b c. (Distributive k, Ob (a :: k), Ob b, Ob c) => (a ** b || a ** c) ~> (a ** (b || c))
distLInv = second @a (lft @k @b @c) ||| second @a (rgt @k @b @c)
distRInv
:: forall {k} a b c. (Distributive k, Ob (a :: k), Ob b, Ob c) => (a ** c || b ** c) ~> ((a || b) ** c)
distRInv = first @c (lft @k @a @b) ||| first @c (rgt @k @a @b)
-- | Distributive promonads seem similar to selective applicative functors.
-- https://blog.veritates.love/selective_applicatives_theoretical_basis.html
branch
:: forall {k} a b c i (p :: k +-> k)
. (DistributiveProfunctor p, Promonad p, Distributive k, Ob a, Ob b, Ob c)
=> p i (a || b) -> p a c -> p b c -> p i c
branch pab pac pbc = rmap codiag ((pac ++ pbc) . pab)
instance Distributive Type where
distL (a, e) = bimap (a,) (a,) e
distR (e, c) = bimap (,c) (,c) e
absorbL = P.snd
absorbR = P.fst
instance Distributive () where
distL = U.Unit
distR = U.Unit
absorbL = U.Unit
absorbR = U.Unit
instance Distributive BOOL where
distL @a @b @c = case obj @a of
Fls -> Fls
Tru -> obj @b +++ obj @c
distR @a @b @c = case obj @c of
Fls -> Fls
Tru -> obj @a +++ obj @b
absorbL = Fls
absorbR = Fls
-- | A product of distributive categories distributes componentwise.
instance (Distributive j, Distributive k) => Distributive (j, k) where
distL @'(a1, a2) @'(b1, b2) @'(c1, c2) = distL @j @a1 @b1 @c1 :**: distL @k @a2 @b2 @c2
distR @'(a1, a2) @'(b1, b2) @'(c1, c2) = distR @j @a1 @b1 @c1 :**: distR @k @a2 @b2 @c2
absorbL @'(a1, a2) = absorbL @j @a1 :**: absorbL @k @a2
absorbR @'(a1, a2) = absorbR @j @a1 :**: absorbR @k @a2
distLClosed
:: forall {k} (a :: k) (b :: k) (c :: k)
. (Closed k, SymMonoidal k, HasBinaryCoproducts k, Ob a, Ob b, Ob c) => (a ** (b || c)) ~> (a ** b || a ** c)
distLClosed = (swap @k @b @a +++ swap @k @c @a) . distRClosed @b @c @a . withObCoprod @k @b @c (swap @k @a @(b || c))
distRClosed
:: forall {k} (a :: k) (b :: k) (c :: k)
. (Closed k, HasBinaryCoproducts k, Ob a, Ob b, Ob c) => ((a || b) ** c) ~> (a ** c || b ** c)
distRClosed =
withOb2 @k @a @c $
withOb2 @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)))
class
(DistributiveProfunctor (p :: k +-> k), MonStrong p, Strong CoprodAction p) =>
StrongDistributiveProfunctor (p :: k +-> k)
instance
(DistributiveProfunctor (p :: k +-> k), MonStrong p, Strong CoprodAction p)
=> StrongDistributiveProfunctor (p :: k +-> k)
-- | The constant functor absorbs a coproduct action: the injected summand is discarded onto
-- the monoid's unit, so this needs only copying\/discarding on the tensor side and coproducts.
instance (CopyDiscard k, HasCoproducts k, Monoid r) => Strong CoprodAction (Rep (Constant r) :: k +-> k) where
act @(COPR a) (Rep @y p) = withObCoprod @k @a @y (Rep (mempty @r . discard @k @a ||| p))
type Traversable :: forall {k}. (k +-> k) -> Constraint
class (Profunctor t) => Traversable (t :: k +-> k) where
traverse :: (StrongDistributiveProfunctor p) => t :.: p :~> p :.: t
-- | With a representable traversable profunctor, you get a traversal a la one-liner.
repTraverse
:: forall {k} (t :: k +-> k) p a b
. (Traversable t, Representable t, StrongDistributiveProfunctor p)
=> p a b -> p (t % a) (t % b)
repTraverse p = p // case traverse (repUniv :.: p) of x :.: y -> rmap (index @t y) x
-- | If both profunctors are representable, you get traversals as in base.
baseTraverse
:: forall {k} (t :: k +-> k) f a b
. (Traversable t, Representable t, Representable f, StrongDistributiveProfunctor f, Ob b)
=> a ~> f % b -> t % a ~> f % (t % b)
baseTraverse = index . repTraverse @t @f @a @b . tabulate
instance (CategoryOf k) => Traversable (Id :: k +-> k) where
traverse (Id f :.: p) = lmap f p :.: Id id \\ p
instance Traversable (->) where
traverse (f :.: p) = lmap f p :.: id
instance (Traversable p, Traversable q) => Traversable (p :.: q) where
traverse ((p :.: q) :.: r) = case traverse (q :.: r) of
r' :.: q' -> case traverse (p :.: r') of
r'' :.: p' -> r'' :.: (p' :.: q')
instance (Traversable p, Traversable q) => Traversable (p :+: q) where
traverse (InjL p :.: r) = case traverse (p :.: r) of r' :.: p' -> r' :.: InjL p'
traverse (InjR q :.: r) = case traverse (q :.: r) of r' :.: q' -> r' :.: InjR q'
type Cotraversable :: forall {k}. (k +-> k) -> Constraint
class (Profunctor t) => Cotraversable (t :: k +-> k) where
cotraverse :: (StrongDistributiveProfunctor (p :: k +-> k)) => p :.: t :~> t :.: p
-- | With a corepresentable cotraversable profunctor, you get a co-traversal a la one-liner.
corepTraverse
:: forall {k} (t :: k +-> k) p a b
. (Cotraversable t, Corepresentable t, StrongDistributiveProfunctor p)
=> p a b -> p (t %% a) (t %% b)
corepTraverse p = p // case cotraverse (p :.: corepUniv) of x :.: y -> lmap (coindex @t x) y
instance (CategoryOf k) => Cotraversable (Id :: k +-> k) where
cotraverse (p :.: Id f) = Id id :.: rmap f p \\ p
instance Cotraversable (->) where
cotraverse (p :.: f) = id :.: rmap f p
instance (Cotraversable p, Cotraversable q) => Cotraversable (p :.: q) where
cotraverse (r :.: (p :.: q)) = case cotraverse (r :.: p) of
p' :.: r' -> case cotraverse (r' :.: q) of
q' :.: r'' -> (p' :.: q') :.: r''
instance (HasBinaryCoproducts k, Cotraversable p, Cotraversable q) => Cotraversable ((p :: k +-> k) :*: q) where
cotraverse (r :.: (p :*: q)) = case (cotraverse (r :.: p), cotraverse (r :.: q)) of
((:.:) @a p' r', (:.:) @b q' r'') -> (rmap (lft @k @a @b) p' :*: rmap (rgt @k @a @b) q') :.: rmap codiag (r' ++ r'') \\ p \\ p' \\ q'
instance (Cotraversable p, Cotraversable q) => Cotraversable (p :+: q) where
cotraverse (r :.: InjL p) = case cotraverse (r :.: p) of p' :.: r' -> InjL p' :.: r'
cotraverse (r :.: InjR q) = case cotraverse (r :.: q) of q' :.: r' -> InjR q' :.: r'
-- | This breaks for possibly infinite traversals like Star [].
instance (Traversable t, Representable t) => Cotraversable (RepCostar t) where
cotraverse (p :.: RepCostar t) = p // case traverse @t (repUniv :.: p) of p' :.: t' -> corepUniv :.: rmap (t . index t') p'
-- | The tensor distributes over coproducts and is absorbed by the initial object:
-- 'distL', 'distR', 'absorbL' and 'absorbR' are isomorphisms, with the inverses 'distLInv',
-- 'distRInv' and 'initiate'.
instance Laws DistributiveStructures where
laws =
inverses "distL" (\ @a @b @c -> Inverses (distL @_ @a @b @c) (label "distLInv" (distLInv @a @b @c)))
P.++ inverses "distR" (\ @a @b @c -> Inverses (distR @_ @a @b @c) (label "distRInv" (distRInv @a @b @c)))
P.++ inverses
"absorbL"
(\ @a -> withOb2 @_ @a @InitialObject (Inverses (absorbL @_ @a) initiate))
P.++ inverses
"absorbR"
(\ @a -> withOb2 @_ @InitialObject @a (Inverses (absorbR @_ @a) initiate))