packages feed

proarrow-0.2.0.0: src/Proarrow/Category/Monoidal/IsoMix.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Isomix categories: dialogue categories whose two units agree, @'Dual' 'Unit'@, the unit of
-- par, being isomorphic to 'Unit' ('dualUnit'). Then a dual and its object can be joined into the
-- unit of the tensor, not just into the unit of par. Every compact closed category is isomix, and
-- so is 'Proarrow.Category.Instance.Linear.LINEAR', where tensor and par still differ.
-- 'Proarrow.Category.Instance.Cps.CPS' @r@ is isomix exactly when the answer object @r@ is the
-- unit: with effects as the answer object, as @CPS (IO ())@, joining a consumer and a value into
-- the unit would discard the effect.
module Proarrow.Category.Monoidal.IsoMix where

import Data.Kind (Constraint)
import Prelude qualified as P

import Proarrow.Category.Instance.Free (Elems, FREE (..), Free (..), HasStructure (..))
import Proarrow.Category.Instance.Product ((:**:) (..))
import Proarrow.Category.Instance.Unit qualified as U
import Proarrow.Category.Monoidal (Monoidal (..), SymMonoidal, UnitF, type (**))
import Proarrow.Category.Monoidal.Dialogue (Dialogue (..), DualF, dualityCounitSA)
import Proarrow.Core (CAT, CategoryOf (..), Kind, Promonad (..))
import Proarrow.Tools.Laws (Inverses (..), Law (..), Laws (..), inverses, (===))

class (Dialogue k) => IsoMix k where
  -- | The unit of par is isomorphic to the unit of the tensor.
  dualUnit :: Dual (Unit :: k) ~> Unit

  -- | The inverse of 'dualUnit'.
  dualUnitInv :: (Unit :: k) ~> Dual Unit

  -- | Join a dual and its object into the unit. 'dualityCounitDefault' gives it from the
  -- dialogue structure; a compact closed category has a counit of its own. (There is no
  -- default method: @a@ occurs only under type families, so GHC could not instantiate one.)
  dualityCounit :: (Ob (a :: k)) => Dual a ** a ~> Unit

-- | 'dualityCounit' from the dialogue structure: into the unit of par, then 'dualUnit'.
dualityCounitDefault :: forall {k} (a :: k). (IsoMix k, Ob a) => Dual a ** a ~> Unit
dualityCounitDefault = dualUnit . dualityCounitSA @a

instance IsoMix () where
  dualUnit = U.Unit
  dualUnitInv = U.Unit
  dualityCounit = U.Unit

instance (IsoMix j, IsoMix k) => IsoMix (j, k) where
  dualUnit = dualUnit :**: dualUnit
  dualUnitInv = dualUnitInv :**: dualUnitInv
  dualityCounit @'(a, a') = dualityCounit @j @a :**: dualityCounit @k @a'

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

instance (IsoMixStructures `Elems` cs) => HasStructure cs (p :: CAT k) IsoMix where
  data Struct IsoMix a b where
    DualUnit :: Struct IsoMix (DualF UnitF) UnitF
    DualUnitInv :: Struct IsoMix UnitF (DualF UnitF)
  foldStructure _ DualUnit = dualUnit
  foldStructure _ DualUnitInv = dualUnitInv
instance P.Show (Struct IsoMix a b) where
  showsPrec _ DualUnit = P.showString "dualUnit"
  showsPrec _ DualUnitInv = P.showString "dualUnitInv"

instance (IsoMixStructures `Elems` cs) => IsoMix (FREE cs (p :: CAT k)) where
  dualUnit = St DualUnit Nil
  dualUnitInv = St DualUnitInv Nil
  dualityCounit @a = dualityCounitDefault @a

-- | 'dualUnit' and 'dualUnitInv' are inverses, and 'dualityCounit' is the one from the
-- dialogue structure.
instance Laws IsoMixStructures where
  laws =
    inverses "dualUnit" (Inverses dualUnit dualUnitInv)
      P.++ [Law "dualityCounit definition" \ @a _ -> withObDual @_ @a (dualityCounit @_ @a === dualityCounitDefault @a)]