packages feed

proarrow-0.1.0.0: src/Proarrow/Category/Monoidal/Closed.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Closed monoidal categories: 'Closed' provides the internal hom @a '~~>' b@, right adjoint to
-- tensoring, with 'curry', 'apply' and functoriality @('^^^')@. Also defines cartesian closed
-- ('CCC') and bicartesian closed ('BiCCC') categories.
module Proarrow.Category.Monoidal.Closed where

import Data.Kind (Constraint, Type)
import Prelude (($))
import Prelude qualified as P

import Proarrow.Category.Instance.Bool (BOOL (..), BoolLeq, Booleans (..))
import Proarrow.Category.Instance.Free
  ( Elem (..)
  , Elems
  , FREE (..)
  , Free (..)
  , HasStructure (..)
  , IsFreeOb (..)
  , Lower
  , WithShow
  , withLowerOb
  )
import Proarrow.Category.Instance.Opposite (OPPOSITE (..), Op (..))
import Proarrow.Category.Instance.Product ((:**:) (..))
import Proarrow.Category.Instance.Unit qualified as U
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..), type (**!))
import Proarrow.Category.Monoidal.Strictified (Fold, Strictified (..), concatMany, obj1, singleton, splitMany, (==))
import Proarrow.Core (CAT, CategoryOf (..), Kind, Profunctor (..), Promonad (..), obj, (//), type (+->))
import Proarrow.Functor (FunctorForRep (..))
import Proarrow.Limit.BinaryProduct ()
import Proarrow.Profunctor.Corepresentable (Corepresentable (..))
import Proarrow.Profunctor.Representable (Rep (..))
import Proarrow.Tools.Laws (Bijection (..), Law (..), Laws (..), bijection, (===))

infixr 2 ~~>

-- | A (right) closed monoidal category: every @b '~~>' c@ is an internal hom, right adjoint to
-- tensoring with @b@. 'curry' and 'Proarrow.Category.Monoidal.Closed.uncurry' witness the
-- adjunction @Hom(a '**' b, c) ≅ Hom(a, b '~~>' c)@.
--
-- __Laws:__
--
-- * @'curry'@ and @'Proarrow.Category.Monoidal.Closed.uncurry'@ are mutually inverse:
--   @'Proarrow.Category.Monoidal.Closed.uncurry' ('curry' f) = f@ and
--   @'curry' ('Proarrow.Category.Monoidal.Closed.uncurry' g) = g@
-- * and natural in all three variables: for @f :: a' '~>' a@, @g :: b' '~>' b@, @h :: c '~>' c'@,
--   @'curry' . 'dimap' (f '**' g) h = 'dimap' f (h '^^^' g) . 'curry'@
--
-- Together these say @'curry'@ is a natural isomorphism, which also forces the familiar
-- @'apply' . ('curry' f '**' 'id') = f@. The exponential is thereby functorial: @'(^^^)'@ is
-- contravariant in its second argument and covariant in its first.
--
-- Checked by 'Proarrow.Testing.Laws.testClosed'.
class (Monoidal k) => Closed k where
  -- | The internal hom (exponential) object.
  type (a :: k) ~~> (b :: k) :: k

  -- | Recovers @'Ob' (a '~~>' b)@ from the objecthood of the ends.
  withObExp :: (Ob (a :: k), Ob b) => ((Ob (a ~~> b)) => r) -> r

  -- | Transposes an arrow out of a tensor into one into an exponential.
  curry :: (Ob (a :: k), Ob b) => a ** b ~> c -> a ~> b ~~> c

  -- | Evaluation: the counit of the adjunction.
  apply :: (Ob (a :: k), Ob b) => (a ~~> b) ** a ~> b

  -- | The exponential's action on arrows: covariant in the result, contravariant in the argument.
  (^^^) :: forall (a :: k) b x y. b ~> y -> x ~> a -> a ~~> b ~> x ~~> y
  f ^^^ g =
    f //
      g //
        withObExp @k @a @b $
          let ab = obj @(a ~~> b) in curry @k @(a ~~> b) @x (f . apply @k @a @b . (ab ** g))

uncurry :: forall {k} b c (a :: k). (Closed k) => (Ob b, Ob c) => a ~> b ~~> c -> a ** b ~> c
uncurry f = apply @k @b @c . (f ** obj @b)

curryS :: forall {k} b c (a :: k). (Closed k) => [a, b] ~> '[c] -> '[a] ~> '[b ~~> c]
curryS (Str f) = withObExp @k @b @c $ Str (curry @k @a @b @c f)

curryS'
  :: forall {k} as c (b :: k). (Closed k, Ob as, Ob b) => (as ** '[b]) ~> '[c] -> as ~> '[b ~~> c]
curryS' f = concatMany == curryS @b @c @(Fold as) (splitMany @as ** obj1 == f)

applyS :: forall {k} (a :: k) b. (Closed k, Ob a, Ob b) => '[a ~~> b, a] ~> '[b]
applyS = withObExp @k @a @b $ Str (apply @k @a @b)

uncurryS :: forall {k} b c (a :: k). (Closed k, Ob b, Ob c) => '[a] ~> '[b ~~> c] -> '[a, b] ~> '[c]
uncurryS f = f ** obj1 == applyS

uncurryS' :: forall {k} as b (c :: k). (Closed k, Ob b, Ob c) => as ~> '[b ~~> c] -> (as ** '[b]) ~> '[c]
uncurryS' f@Str{} = concatMany @as ** obj1 == uncurryS @b @c @(Fold as) (splitMany == f)

compS :: forall {k} (a :: k) b c. (Closed k, Ob a, Ob b, Ob c) => '[b ~~> c, a ~~> b] ~> '[a ~~> c]
compS =
  withObExp @k @b @c $
    withObExp @k @a @b $
      curryS' (obj1 ** applyS @a @b == applyS @b @c)

comp :: forall {k} (a :: k) b c. (Closed k, Ob a, Ob b, Ob c) => (b ~~> c) ** (a ~~> b) ~> a ~~> c
comp = unStr (compS @a @b @c)

mkExponentialS :: forall {k} (a :: k) b. (Closed k) => '[a] ~> '[b] -> '[] ~> '[a ~~> b]
mkExponentialS f@Str{} = curryS' f

mkExponential :: forall {k} a b. (Closed k) => (a :: k) ~> b -> Unit ~> (a ~~> b)
mkExponential ab = unStr (mkExponentialS (singleton ab))

lowerS :: forall {k} (a :: k) b. (Closed k, Ob a, Ob b) => ('[] ~> '[a ~~> b]) -> '[a] ~> '[b]
lowerS = uncurryS'

lower :: forall {k} (a :: k) b. (Closed k, Ob a, Ob b) => (Unit ~> (a ~~> b)) -> a ~> b
lower f = unStr (lowerS (Str f)) \\ f

toEl :: forall {k} (a :: k). (Closed k, Ob a) => a ~> Unit ~~> a
toEl = curry @k @a @Unit @a rightUnitor

instance Closed Type where
  type a ~~> b = a -> b
  withObExp r = r
  curry = P.curry
  apply = P.uncurry id
  (^^^) = P.flip dimap

instance Closed () where
  type '() ~~> '() = '()
  withObExp r = r
  curry U.Unit = U.Unit
  apply = U.Unit
  U.Unit ^^^ U.Unit = U.Unit

-- | Implication is the internal hom of the walking arrow: @a ~~> b@ is @'BoolLeq' a b@.
instance Closed BOOL where
  type a ~~> b = BoolLeq a b
  withObExp @a @b r = case (obj @a, obj @b) of
    (Fls, Fls) -> r
    (Fls, Tru) -> r
    (Tru, Fls) -> r
    (Tru, Tru) -> r
  curry @a @b @c f =
    ( case (obj @a, obj @b, obj @c) of
        (Fls, Fls, Fls) -> F2T
        (Fls, Fls, Tru) -> F2T
        (Fls, Tru, Fls) -> Fls
        (Fls, Tru, Tru) -> F2T
        (Tru, Fls, Fls) -> Tru
        (Tru, Fls, Tru) -> Tru
        (Tru, Tru, Fls) -> case f of {}
        (Tru, Tru, Tru) -> Tru
    )
      \\ f
  apply @a @b = case (obj @a, obj @b) of
    (Fls, Fls) -> Fls
    (Fls, Tru) -> F2T
    (Tru, Fls) -> Fls
    (Tru, Tru) -> Tru

instance (Closed j, Closed k) => Closed (j, k) where
  type '(a1, a2) ~~> '(b1, b2) = '(a1 ~~> b1, a2 ~~> b2)
  withObExp @'(a1, a2) @'(b1, b2) r = withObExp @j @a1 @b1 (withObExp @k @a2 @b2 r)
  curry @'(a1, a2) @'(b1, b2) (f1 :**: f2) = curry @j @a1 @b1 f1 :**: curry @k @a2 @b2 f2
  apply @'(a1, a2) @'(b1, b2) = apply @j @a1 @b1 :**: apply @k @a2 @b2
  (f1 :**: f2) ^^^ (g1 :**: g2) = (f1 ^^^ g1) :**: (f2 ^^^ g2)

data family ExpRep :: (OPPOSITE k, k) +-> k
instance (Closed k) => FunctorForRep (ExpRep :: (OPPOSITE k, k) +-> k) where
  type ExpRep @ '(OP a, b) = a ~~> b
  fmap (Op f :**: g) = g ^^^ f

data family Not (r :: k) :: OPPOSITE k +-> k
instance (Closed k, Ob r) => FunctorForRep (Not (r :: k)) where
  type Not r @ OP a = a ~~> r
  fmap (Op f) = obj @r ^^^ f

-- | The "reader"\/exponential-by-@m@ functor, covariant unlike 'Not' (which fixes the codomain).
data family Exp (m :: k) :: k +-> k

instance (Closed k, Ob m) => FunctorForRep (Exp m :: k +-> k) where
  type Exp m @ a = m ~~> a
  fmap f = f ^^^ obj @m

-- | The Op-Op adjunction, giving rise to the continuation monad.
instance (Closed k, SymMonoidal k, Ob r) => Corepresentable (Rep (Not (r :: k))) where
  type Rep (Not r) %% a = OP (a ~~> r)
  cotabulate (Op f) = Rep (swapClosed @r f) \\ f
  coindex (Rep f) = Op (swapClosed @r f)
  corepMap f = Op (obj @r ^^^ f)

swapClosed :: forall {k} (c :: k) a b. (Closed k, SymMonoidal k, Ob b, Ob c) => a ~> b ~~> c -> b ~> a ~~> c
swapClosed f = curry @k @b @a (uncurry @b @c f . swap @k @b @a) \\ f

data family (-->) (a :: k) (b :: k) :: k

-- | The structures the free category needs for 'Closed', and those its laws are stated for.
type ClosedStructures :: [Kind -> Constraint]
type ClosedStructures = '[Monoidal, Closed]

instance (IsFreeOb (a :: FREE cs p), IsFreeOb b, ClosedStructures `Elems` cs) => IsFreeOb (a --> b) where
  type Lower f (a --> b) = Lower f a ~~> Lower f b
  lowerOb @k' @f r = fromAll @Closed @cs @k' (withLowerOb @f @a (withLowerOb @f @b (withObExp @k' @(Lower f a) @(Lower f b) r)))
instance (ClosedStructures `Elems` cs) => HasStructure cs (p :: CAT k) Closed where
  data Struct Closed a b where
    Apply :: (Ob a, Ob b) => Struct Closed ((a --> b) **! a) b
    Curry :: forall a b c. (Ob a, Ob b) => (a **! b) ~> c -> Struct Closed a (b --> c)
  foldStructure @f _ (Apply @a @b) = withLowerOb @f @a (withLowerOb @f @b (apply @_ @(Lower f a) @(Lower f b)))
  foldStructure @f go (Curry @a @b f) = withLowerOb @f @a (withLowerOb @f @b (curry @_ @(Lower f a) @(Lower f b) (go f)))
instance (WithShow a) => P.Show (Struct Closed a b) where
  showsPrec _ Apply = P.showString "apply"
  showsPrec d (Curry f) = P.showParen (d P.> 10) $ P.showString "curry " . P.showsPrec 11 f

instance (ClosedStructures `Elems` cs) => Closed (FREE cs (p :: CAT k)) where
  type a ~~> b = a --> b
  withObExp r = r
  curry f = St (Curry f) Nil \\ f
  apply = St Apply Nil

-- | 'apply' undoes 'curry' and every arrow into an exponential is the 'curry' of one ('curry' is
-- a bijection with inverse @f |-> 'apply' . (f '**' 'id')@), 'curry' is natural in all three
-- objects, and '^^^' is the exponential's action on arrows defined from 'curry' and 'apply'.
-- Together these make @(- ** b)@ left adjoint to @(b ~~> -)@, and '^^^' a profunctor.
instance Laws ClosedStructures where
  laws =
    bijection
      "curry"
      ( \ @a @b @c mor ->
          withOb2 @_ @a @b $
            withObExp @_ @b @c $
              Bijection (mor @(a ** b) @c "p") (mor @a @(b ~~> c) "q") (curry @_ @a @b) (\q -> apply @_ @b @c . (q ** obj @b))
      )
      P.++ [ Law "curry naturality" \ @a @b @c @d @e mor -> withOb2 @_ @a @b $ withOb2 @_ @d @d do
               p <- mor @(a ** b) @c "p"
               f <- mor @d @a "f"
               g <- mor @d @b "g"
               h <- mor @c @e "h"
               (h ^^^ g) . curry @_ @a @b p . f === curry @_ @d @d (h . p . (f ** g))
           , Law "internal hom on arrows" \ @a @b @c @d mor -> do
               f <- mor @b @d "f"
               g <- mor @c @a "g"
               withObExp @_ @a @b (f ^^^ g === curry @_ @(a ~~> b) @c (f . apply @_ @a @b . (obj @(a ~~> b) ** g)))
           ]