packages feed

proarrow-0.2.0.0: src/Proarrow/Category/Instance/Cps.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | A closed symmetric monoidal category with a chosen answer object @r@ is a dialogue category,
-- with @'Dual' a = a '~~>' r@. 'CPS' wraps the category to say which object. The morphisms are
-- those of the category itself, so with @r@ an object of effects, as @IO ()@ in 'Data.Kind.Type',
-- a morphism is pure and a term of @'Proarrow.Tools.SMC.Up' a@ is a computation
-- @(a -> IO ()) -> IO ()@: call by push value, with the effects in the computations only.
--
-- @CPS r@ is isomix exactly when @r@ is the unit ('answerUnit'), and *-autonomous only in
-- degenerate cases, since @(a ~~> r) ~~> r@ is rarely @a@.
module Proarrow.Category.Instance.Cps (CPS (..), Cps (..), answerUnit) where

import Prelude (type (~))

import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..))
import Proarrow.Category.Monoidal.Closed (Closed (..), swapClosed, toEl, uncurry)
import Proarrow.Category.Monoidal.Dialogue (Dialogue (..))
import Proarrow.Category.Monoidal.IsoMix (IsoMix (..))
import Proarrow.Core (CAT, CategoryOf (..), Profunctor (..), Promonad (..), UN, WrappedOb, dimapDefault, obj)
import Proarrow.Monoid (CocommutativeComonoid, Comonoid (..))
import Proarrow.Optic (PIso', iso)

type data CPS (r :: k) = C k

-- | The arrows of the category, wrapped as a category on the 'CPS'-wrapped kind.
type Cps :: CAT (CPS r)
data Cps (a :: CPS r) b where
  Cps :: {unCps :: a ~> b} -> Cps (C a :: CPS r) (C b)

instance (CategoryOf k) => Profunctor (Cps :: CAT (CPS (r :: k))) where
  dimap = dimapDefault
  r \\ Cps f = r \\ f

instance (CategoryOf k) => Promonad (Cps :: CAT (CPS (r :: k))) where
  id = Cps id
  Cps f . Cps g = Cps (f . g)

instance (CategoryOf k) => CategoryOf (CPS (r :: k)) where
  type (~>) = Cps
  type Ob a = WrappedOb C a

instance (Monoidal k) => MonoidalProfunctor (Cps :: CAT (CPS (r :: k))) where
  one = Cps one
  Cps f ** Cps g = Cps (f ** g)

instance (Monoidal k) => Monoidal (CPS (r :: k)) where
  type Unit = C Unit
  type a ** b = C (UN C a ** UN C b)
  withOb2 @(C a) @(C b) r = withOb2 @k @a @b r
  leftUnitor @(C a) = Cps (leftUnitor @k @a)
  leftUnitorInv @(C a) = Cps (leftUnitorInv @k @a)
  rightUnitor @(C a) = Cps (rightUnitor @k @a)
  rightUnitorInv @(C a) = Cps (rightUnitorInv @k @a)
  associator @(C a) @(C b) @(C c) = Cps (associator @k @a @b @c)
  associatorInv @(C a) @(C b) @(C c) = Cps (associatorInv @k @a @b @c)

instance (SymMonoidal k) => SymMonoidal (CPS (r :: k)) where
  swap @(C a) @(C b) = Cps (swap @k @a @b)

instance (Closed k) => Closed (CPS (r :: k)) where
  type a ~~> b = C (UN C a ~~> UN C b)
  withObExp @(C a) @(C b) r = withObExp @k @a @b r
  curry @(C a) @(C b) (Cps f) = Cps (curry @k @a @b f)
  apply @(C a) @(C b) = Cps (apply @k @a @b)
  Cps f ^^^ Cps g = Cps (f ^^^ g)

-- | The dual of @a@ is @a '~~>' r@, the functor 'Proarrow.Category.Monoidal.Closed.Not' @r@ on
-- objects: linear distribution is uncurrying, reassociating and currying.
instance (Closed k, SymMonoidal k, Ob r) => Dialogue (CPS (r :: k)) where
  type Dual (a :: CPS r) = C (UN C a ~~> r)
  withObDual @(C a) r' = withObExp @k @a @r r'
  dual (Cps f) = Cps (obj @r ^^^ f)
  linDist @(C a) @(C b) @(C c) (Cps f) =
    withOb2 @k @b @c (Cps (curry @k @a @(b ** c) (uncurry @c @r f . associatorInv @k @a @b @c)))
  linDistInv @(C a) @(C b) @(C c) (Cps f) =
    withOb2 @k @a @b (withOb2 @k @b @c (Cps (curry @k @(a ** b) @c (uncurry @(b ** c) @r f . associator @k @a @b @c))))
  doubleNegInv @(C a) = withObExp @k @a @r (Cps (swapClosed @r (obj @(a ~~> r))))

-- | With the unit as the answer object, @'Dual' 'Unit' = Unit ~~> Unit ≅ Unit@, and a consumer meets
-- its value in 'apply'. With any other answer object the units differ, so this is the only isomix
-- instance; the constraint on @r@ says so, since 'Unit' is a type family and cannot head an
-- instance.
instance (Closed k, SymMonoidal k, r ~ Unit) => IsoMix (CPS (r :: k)) where
  dualUnit = withObExp @k @Unit @Unit (Cps (apply @k @Unit @Unit . rightUnitorInv @k @(Unit ~~> Unit)))
  dualUnitInv = Cps (toEl @Unit)
  dualityCounit @(C a) = Cps (apply @k @a @Unit)

-- | The converse: an isomix structure on @CPS r@ makes @r@ the unit, through @Unit ~~> r ≅ r@.
answerUnit :: forall {k} (r :: k). (Closed k, Ob r, IsoMix (CPS r)) => PIso' r Unit
answerUnit =
  withObExp @k @Unit @r
    ( iso
        (unCps (dualUnit @(CPS r)) . toEl @r)
        (apply @k @Unit @r . rightUnitorInv @k @(Unit ~~> r) . unCps (dualUnitInv @(CPS r)))
    )

instance (Comonoid a) => Comonoid (C a :: CPS r) where
  counit = Cps counit
  comult = Cps comult

instance (CocommutativeComonoid a) => CocommutativeComonoid (C a :: CPS r)