proarrow-0.2.0.0: src/Proarrow/Category/Monoidal/Dialogue.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
-- | Dialogue categories (Melliès): symmetric monoidal categories with a tensorial negation 'Dual',
-- where morphisms @a ** b ~> Dual c@ correspond to @a ~> Dual (b ** c)@ ('linDist'). Unlike in a
-- *-autonomous category, double negation @'Dual' ('Dual' a) ~> a@ need not exist: only its inverse
-- 'doubleNegInv' does, which 'tripleNeg' undoes on a dual. Any closed category with a chosen
-- answer object is one, with @'Dual' a = a ~~> r@, which is why the continuation passing
-- reading of System L in "Proarrow.Tools.SMC" needs no more than this.
--
-- The *-autonomous categories of "Proarrow.Category.Monoidal.StarAutonomous" are the dialogue
-- categories whose double negation is an isomorphism.
module Proarrow.Category.Monoidal.Dialogue where
import Data.Kind (Constraint)
import Prelude (($))
import Prelude qualified as P
import Proarrow.Category.Instance.Bool (BOOL (..), Booleans (..), Not)
import Proarrow.Category.Instance.Free
( Elem (..)
, Elems
, FREE (..)
, Free (..)
, HasStructure (..)
, IsFreeOb (..)
, Lower
, WithShow
, withLowerOb
)
import Proarrow.Category.Instance.Product ((:**:) (..))
import Proarrow.Category.Instance.Unit qualified as U
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..), swap, type (**!))
import Proarrow.Category.Monoidal.Strictified (Strictified (..), obj1, singleton)
import Proarrow.Core (CAT, CategoryOf (..), Kind, Obj, Profunctor (..), Promonad (..), obj)
import Proarrow.Limit.BinaryProduct ()
import Proarrow.Tools.Laws (Bijection (..), Law (..), Laws (..), bijection, (===))
-- | A dialogue category: a symmetric monoidal category with a tensorial negation, so that 'Dual'
-- is a contravariant functor and @Hom(a '**' b, 'Dual' c) ≅ Hom(a, 'Dual' (b '**' c))@.
--
-- __Laws:__
--
-- * 'dual' is a contravariant functor: @'dual' 'id' = 'id'@ and @'dual' (f . g) = 'dual' g . 'dual' f@
-- * 'linDist' and 'linDistInv' are mutually inverse, giving
-- @Hom(a '**' b, 'Dual' c) ≅ Hom(a, 'Dual' (b '**' c))@, natural in all three variables
-- * 'doubleNegInv' is 'doubleNegInvDefault', the one the rest of the structure gives
--
-- Stated as code by the 'Proarrow.Tools.Laws.Laws' instance for 'DialogueStructures', and
-- checked by @Proarrow.Testing.Laws.testDialogue@.
class (SymMonoidal k) => Dialogue k where
-- | The dual of an object.
type Dual (a :: k) :: k
-- | Recovers @'Ob' ('Dual' a)@ from the objecthood of @a@.
withObDual :: (Ob (a :: k)) => ((Ob (Dual a)) => r) -> r
-- | 'Dual'\'s contravariant action on arrows.
dual :: (a :: k) ~> b -> Dual b ~> Dual a
-- | Linear distribution: transposes a tensor factor across the dual.
linDist :: (Ob (a :: k), Ob b, Ob c) => a ** b ~> Dual c -> a ~> Dual (b ** c)
-- | Inverse to 'linDist'.
linDistInv :: (Ob (a :: k), Ob b, Ob c) => a ~> Dual (b ** c) -> a ** b ~> Dual c
-- | Double-negation introduction. Defaults to 'doubleNegInvDefault'.
doubleNegInv :: (Ob (a :: k)) => a ~> Dual (Dual a)
doubleNegInv @a = doubleNegInvDefault @a
dualObj :: forall {k} (a :: k). (Dialogue k, Ob a) => Obj (Dual a)
dualObj = dual (obj @a)
-- | 'doubleNegInv' from the rest of the structure, through 'linDistInv' and the duality unit.
doubleNegInvDefault :: forall {k} (a :: k). (Dialogue k, Ob a) => a ~> Dual (Dual a)
doubleNegInvDefault =
linDistInv @k @Unit @a @(Dual a) (dual (swap @k @a @(Dual a)) . dualityUnitSA @a) . leftUnitorInv @k @a
\\ dualObj @a
-- | Triple negation elimination: a dual is a retract of its double negation, with 'doubleNegInv' as
-- the section, @'tripleNeg' . 'doubleNegInv' = 'id'@. For the computations of "Proarrow.Tools.SMC"
-- it runs a computation of a computation into one, like @join@. It is an isomorphism only in a
-- *-autonomous category: in 'Data.Kind.Type' with answer object 'Prelude.Bool', @Dual ()@ has two
-- elements and @Dual (Dual (Dual ()))@ sixteen.
tripleNeg :: forall {k} (a :: k). (Dialogue k, Ob a) => Dual (Dual (Dual a)) ~> Dual a
tripleNeg = dual (doubleNegInv @k @a)
-- | The Kleisli extension of the double negation monad at a dual: a morphism out of @a@ into a dual,
-- extended to double negations of @a@. This is the bind of the continuation reading of System L in
-- "Proarrow.Tools.SMC". It moves @a@ to the other side of the hom, @g ** y ~> Dual a@, and dualizes.
{-# INLINE bindDual #-}
bindDual
:: forall {k} (g :: k) a y
. (Dialogue k, Ob g, Ob a, Ob y)
=> g ** a ~> Dual y
-> Dual (Dual a) ** g ~> Dual y
bindDual f =
withObDual @k @a $
withObDual @k @(Dual a) $
linDistInv @k @(Dual (Dual a)) @g @y $
dual $
linDistInv @k @g @y @a (dual (swap @k @y @a) . linDist @k @g @a @y f)
linDistS
:: forall {k} (a :: k) (b :: k) c. (Dialogue k, Ob c) => '[a, b] ~> '[Dual c] -> '[a] ~> '[Dual (b ** c)]
linDistS f@Str{} = singleton (linDist @k @a @b @c (unStr f))
linDistInvS
:: forall {k} (a :: k) (b :: k) c. (Dialogue k, Ob b, Ob c) => '[a] ~> '[Dual (b ** c)] -> '[a, b] ~> '[Dual c]
linDistInvS f@Str{} = withObDual @k @c (Str (linDistInv @k @a @b @c (unStr f)) \\ obj1 @(Dual c))
-- | Par, the dual of the tensor of the duals.
type Par :: forall {k}. k -> k -> k
type Par a b = Dual (Dual a ** Dual b)
-- | Recovers @'Ob' ('Par' a b)@, and the objecthood of the duals it is made of, from the objecthood
-- of @a@ and @b@.
withObPar
:: forall {k} (a :: k) b r. (Dialogue k, Ob a, Ob b) => ((Ob (Dual a), Ob (Dual b), Ob (Par a b)) => r) -> r
withObPar r = withObDual @k @a (withObDual @k @b (withOb2 @k @(Dual a) @(Dual b) (withObDual @k @(Dual a ** Dual b) r)))
-- | 'Par'\'s action on arrows.
par :: forall {k} (a :: k) b c d. (Dialogue k) => a ~> c -> b ~> d -> Par a b ~> Par c d
par f g = dual (dual f ** dual g)
-- | The symmetry of 'Par'.
parSwap :: forall {k} (a :: k) b. (Dialogue k, Ob a, Ob b) => Par a b ~> Par b a
parSwap = withObPar @a @b (dual (swap @k @(Dual b) @(Dual a)))
-- | Linear distributivity: the tensor distributes into the left of a 'Par'. Given the dual of
-- @a '**' b@, the @a@ turns it into the dual of @b@, which the 'Par' answers with @c@.
weakDistL :: forall {k} (a :: k) b c. (Dialogue k, Ob a, Ob b, Ob c) => a ** Par b c ~> Par (a ** b) c
weakDistL =
withObPar @b @c
( withOb2 @k @a @b
( withObDual @k @(a ** b)
( withOb2 @k @a @(Par b c)
( linDist @k @(a ** Par b c) @(Dual (a ** b)) @(Dual c)
( linDistInv @k @(Par b c) @(Dual b) @(Dual c) id
. (obj @(Par b c) ** linDistInv @k @(Dual (a ** b)) @a @b id)
. (obj @(Par b c) ** swap @k @a @(Dual (a ** b)))
. associator @k @(Par b c) @a @(Dual (a ** b))
. (swap @k @a @(Par b c) ** obj @(Dual (a ** b)))
)
)
)
)
)
-- | Linear distributivity on the other side, from 'weakDistL' by symmetry.
weakDistR :: forall {k} (a :: k) b c. (Dialogue k, Ob a, Ob b, Ob c) => Par a b ** c ~> Par a (b ** c)
weakDistR =
withObPar @b @a
( withOb2 @k @c @b
( par (obj @a) (swap @k @c @b)
. parSwap @(c ** b) @a
. weakDistL @c @b @a
. swap @k @(Par b a) @c
. (parSwap @a @b ** obj @c)
)
)
dualityUnitSA :: forall {k} (a :: k). (Dialogue k, Ob a) => Unit ~> Dual (Dual a ** a)
dualityUnitSA = linDist @k @_ @(Dual a) @a leftUnitor \\ dualObj @a
dualityCounitSA :: forall {k} (a :: k). (Dialogue k, Ob a) => Dual a ** a ~> Dual Unit
dualityCounitSA = linDistInv @k @(Dual a) @a @Unit (dual (rightUnitor @k @a)) \\ dualObj @a
instance Dialogue () where
type Dual '() = '()
withObDual r = r
dual U.Unit = U.Unit
linDist U.Unit = U.Unit
linDistInv U.Unit = U.Unit
doubleNegInv = U.Unit
instance Dialogue BOOL where
type Dual (a :: BOOL) = Not a
withObDual r = r
dual Fls = Tru
dual F2T = F2T
dual Tru = Fls
linDist @a @b f = case (obj @a, obj @b) of
(Fls, Fls) -> F2T
(Tru, Fls) -> Tru
(_, Tru) -> f
linDistInv @_ @b @c f = case (obj @b, obj @c) of
(Fls, Fls) -> F2T
(Fls, Tru) -> Fls
(Tru, _) -> f
doubleNegInv @a = case obj @a of Fls -> Fls; Tru -> Tru
instance (Dialogue j, Dialogue k) => Dialogue (j, k) where
type Dual '(a, b) = '(Dual a, Dual b)
withObDual @'(a, b) r = withObDual @j @a (withObDual @k @b r)
dual (f :**: g) = dual f :**: dual g
linDist @'(a1, a2) @'(b1, b2) @'(c1, c2) (f :**: g) = linDist @j @a1 @b1 @c1 f :**: linDist @k @a2 @b2 @c2 g
linDistInv @'(a1, a2) @'(b1, b2) @'(c1, c2) (f :**: g) = linDistInv @j @a1 @b1 @c1 f :**: linDistInv @k @a2 @b2 @c2 g
doubleNegInv @'(a, b) = doubleNegInv @j @a :**: doubleNegInv @k @b
data family DualF (a :: k) :: k
instance (IsFreeOb (a :: FREE cs p), Dialogue `Elem` cs) => IsFreeOb (DualF a) where
type Lower f (DualF a) = Dual (Lower f a)
lowerOb @k' @f r = fromAll @Dialogue @cs @k' (withLowerOb @f @a (withObDual @k' @(Lower f a) r))
-- | The structures the free category needs for 'Dialogue', and those its laws are stated for.
type DialogueStructures :: [Kind -> Constraint]
type DialogueStructures = '[Monoidal, SymMonoidal, Dialogue]
instance
(DialogueStructures `Elems` cs)
=> HasStructure cs (p :: CAT k) Dialogue
where
data Struct Dialogue a b where
Dual :: a ~> b -> Struct Dialogue (DualF b) (DualF a)
LinDist :: (Ob a, Ob b, Ob c) => a **! b ~> DualF c -> Struct Dialogue a (DualF (b **! c))
LinDistInv :: (Ob a, Ob b, Ob c) => a ~> DualF (b **! c) -> Struct Dialogue (a **! b) (DualF c)
foldStructure go (Dual f) = dual (go f)
foldStructure @f go (LinDist @a @b @c g) =
withLowerOb @f @a (withLowerOb @f @b (withLowerOb @f @c (linDist @_ @(Lower f a) @(Lower f b) @(Lower f c) (go g))))
foldStructure @f go (LinDistInv @a @b @c g) =
withLowerOb @f @a (withLowerOb @f @b (withLowerOb @f @c (linDistInv @_ @(Lower f a) @(Lower f b) @(Lower f c) (go g))))
instance (WithShow a) => P.Show (Struct Dialogue a b) where
showsPrec d (Dual f) = P.showParen (d P.> 10) P.$ P.showString "dual " . P.showsPrec 11 f
showsPrec d (LinDist f) = P.showParen (d P.> 10) P.$ P.showString "linDist " . P.showsPrec 11 f
showsPrec d (LinDistInv f) = P.showParen (d P.> 10) P.$ P.showString "linDistInv " . P.showsPrec 11 f
instance
(DialogueStructures `Elems` cs)
=> Dialogue (FREE cs (p :: CAT k))
where
type Dual a = DualF a
withObDual r = r
dual f = St (Dual f) Nil \\ f
linDist @a @b @c f = St (LinDist @a @b @c f) Nil \\ f
linDistInv @a @b @c f = St (LinDistInv @a @b @c f) Nil \\ f
-- | 'dual' is a contravariant functor, 'linDist' is a natural bijection
-- @Hom(a ** b, Dual c) ≅ Hom(a, Dual (b ** c))@ with inverse 'linDistInv', and 'doubleNegInv' is
-- the one they give.
instance Laws DialogueStructures where
laws =
[ Law "dual identity" \ @a _ -> withObDual @_ @a (dual (obj @a) === id)
, Law "dual composition" \ @a @b @c mor -> do
f <- mor @a @b "f"
g <- mor @b @c "g"
dual (g . f) === dual f . dual g
, Law "linDist naturality" \ @a @b @c @d @e mor ->
withOb2 @_ @a @b $ withOb2 @_ @d @e $ withObDual @_ @c $ withObDual @_ @d do
p <- mor @(a ** b) @(Dual c) "p"
f <- mor @d @a "f"
g <- mor @e @b "g"
h <- mor @d @c "h"
linDist @_ @d @e @d (dual h . p . (f ** g)) === dual (g ** h) . linDist @_ @a @b @c p . f
]
P.++ bijection
"linDist"
( \ @a @b @c mor ->
withOb2 @_ @a @b $
withOb2 @_ @b @c $
withObDual @_ @c $
withObDual @_ @(b ** c) $
Bijection (mor @(a ** b) @(Dual c) "p") (mor @a @(Dual (b ** c)) "q") (linDist @_ @a @b @c) (linDistInv @_ @a @b @c)
)
P.++ [ Law "doubleNegInv definition" \ @a _ -> withObDual @_ @a $ withObDual @_ @(Dual a) (doubleNegInv @_ @a === doubleNegInvDefault @a)
]