packages feed

proarrow-0.1.0.0: src/Proarrow/Profunctor/Free.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | Free constructions: 'HasFree' captures the object constraints @ob@ whose forgetful functor has a left
-- adjoint, with @Free ob@ the free object, 'lift' the unit and 'foldMap' the universal property of the
-- adjunction (packaged as a 'Corepresentable' heteromorphism profunctor).
module Proarrow.Profunctor.Free where

import Data.Foldable1 (Foldable1 (foldMap1))
import Data.Kind (Constraint, Type)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (Maybe (..))
import Prelude (($))
import Prelude qualified as P

import Proarrow.Category.Instance.Free (FREE (..), IsFreeOb (..), liftFree, retractFree)
import Proarrow.Category.Instance.IntConstruction (INT (..), IntConstruction (..), toInt)
import Proarrow.Category.Instance.Nat (Nat (..), first)
import Proarrow.Category.Instance.Prof (Prof (..))
import Proarrow.Category.Instance.Sub (Forget, On, SUBCAT (..), Sub (..))
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), swap)
import Proarrow.Category.Monoidal.Applicative (Applicative (..))
import Proarrow.Category.Monoidal.CompactClosed (CompactClosed (..))
import Proarrow.Category.Monoidal.StarAutonomous (Dual, dualObj)
import Proarrow.Category.Monoidal.Strength (TracedMonoidal)
import Proarrow.Category.Monoidal.Strictified (Fold, Strictified (..), (==))
import Proarrow.Core
  ( CAT
  , CategoryOf (..)
  , Hom
  , Kind
  , OB
  , Profunctor (..)
  , Promonad (..)
  , UN
  , arr
  , lmap
  , obj
  , rmap
  , tgt
  , (//)
  , (:~>)
  )
import Proarrow.Functor (Functor (..))
import Proarrow.Limit.BinaryProduct (HasBinaryProducts)
import Proarrow.Limit.Terminal (HasTerminalObject)
import Proarrow.Monoid (Monoid)
import Proarrow.Monoid qualified as M
import Proarrow.Profunctor.Corepresentable (Corepresentable (..))
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Instance.Identity (Id)
import Proarrow.Profunctor.Instance.List (LIST (..), List (..))
import Proarrow.Profunctor.Instance.Star (Star, pattern Star)
import Proarrow.Profunctor.Representable (Rep (..))

type HasFree :: forall {k}. OB k -> Constraint
class (CategoryOf k, forall a. (Ob a) => ob (Free ob a)) => HasFree (ob :: OB k) where
  type Free ob (a :: k) :: k
  lift :: (Ob a) => a ~> Free ob a
  foldMap :: (ob b) => (a ~> b) -> Free ob a ~> b

retract :: forall ob a. (HasFree ob, ob a, Ob a) => Free ob a ~> a
retract = foldMap @ob id

freeMap :: (HasFree ob) => (a ~> b) -> Free ob a ~> Free ob b
freeMap @ob f = foldMap @ob (lift @ob . f) \\ f

freeComp :: (HasFree ob, Ob c) => b ~> Free ob c -> a ~> Free ob b -> a ~> Free ob c
freeComp @ob l r = foldMap @ob l . r

-- | By creating the left adjoint to the forgetful functor,
-- we obtain the free-forgetful adjunction.
instance (HasFree ob) => Corepresentable (Rep (Forget (ob :: OB k))) where
  type Rep (Forget ob) %% a = SUB (Free ob a)
  coindex (Rep f) = Sub (foldMap @ob f) \\ f
  corepUniv @a = let f = lift @ob @a in Rep f \\ f

instance HasFree P.Monoid where
  type Free P.Monoid a = [a]
  lift = P.pure
  foldMap = P.foldMap

instance HasFree P.Semigroup where
  type Free P.Semigroup a = NonEmpty a
  lift = P.pure
  foldMap = foldMap1

instance HasFree (P.Monoid `On` P.Semigroup) where
  type Free (P.Monoid `On` P.Semigroup) (SUB a) = SUB (Maybe a)
  lift = Sub Just
  foldMap (Sub f) = Sub (P.foldMap f)

-- | The free 'Applicative' on a functor @f@ (the 'HasFree' instance for 'Applicative'): formal 'pure',
-- effect and 'liftA2' nodes, retracted into any applicative by 'retractAp'.
type Ap :: (k -> Type) -> k -> Type
data Ap f a where
  Pure :: Unit ~> a -> Ap f a
  Eff :: f a -> Ap f a
  LiftA2 :: (Ob a, Ob b) => (a ** b ~> c) -> Ap f a -> Ap f b -> Ap f c

instance (CategoryOf k, Functor f) => Functor (Ap (f :: k -> Type)) where
  map f (Pure a) = Pure (f . a)
  map f (Eff x) = Eff (map f x)
  map f (LiftA2 k x y) = LiftA2 (f . k) x y

instance Functor Ap where
  map (Nat n) = Nat $ \case
    Pure a -> Pure a
    Eff fa -> Eff (n fa)
    LiftA2 k x y -> LiftA2 k (first (Nat n) x) (first (Nat n) y)

instance (Monoidal k) => Promonad (Star Ap :: CAT (k -> Type)) where
  id = Star (lift @Applicative)
  Star l . Star r = Star (freeComp @Applicative l r)

instance (Monoidal k, Functor f) => Applicative (Ap (f :: k -> Type)) where
  pure a () = Pure a
  liftA2 f (fa, fb) = LiftA2 f fa fb

-- | Given as 'P.Semigroup'\/'P.Monoid' rather than as 'Monoid' directly: @'Ap' f m@ is of kind
-- @Type@, where 'Monoid' already comes from the blanket @'P.Monoid' m => 'Monoid' (m :: Type)@
-- instance, so defining it here too would make every use overlap and solve to neither.
instance (Monoidal k, Monoid m) => P.Semigroup (Ap (f :: k -> Type) m) where
  l <> r = LiftA2 M.mappend l r

instance (Monoidal k, Monoid m) => P.Monoid (Ap (f :: k -> Type) m) where
  mempty = Pure M.mempty

retractAp :: (Applicative f) => Ap f a -> f a
retractAp (Pure a) = pure a ()
retractAp (Eff fa) = fa
retractAp (LiftA2 k x y) = liftA2 k (retractAp x, retractAp y)

instance (Monoidal k) => HasFree (Applicative :: OB (k -> Type)) where
  type Free Applicative f = Ap f
  lift = Nat Eff
  foldMap f@Nat{} = Nat retractAp . map f

-- | The free 'Promonad' on a profunctor @p@: a chain of @p@s ending in a hom arrow, folded into any
-- promonad by 'foldFreePromonad'.
data FreePromonad p a b where
  Unit :: (a ~> b) -> FreePromonad p a b
  Comp :: p a b -> FreePromonad p b c -> FreePromonad p a c

freePromonadAlg :: p :.: FreePromonad p :~> FreePromonad p
freePromonadAlg (p :.: pp) = Comp p pp

foldFreePromonad :: (Promonad q) => p :~> q -> FreePromonad p :~> q
foldFreePromonad _ (Unit f) = arr f
foldFreePromonad n (Comp p pp) = foldFreePromonad n pp . n p

instance (Profunctor p) => Profunctor (FreePromonad p) where
  dimap l r (Unit f) = Unit (r . f . l)
  dimap l r (Comp p q) = Comp (lmap l p) (rmap r q)
  r \\ (Unit f) = r \\ f
  r \\ Comp p q = r \\ p \\ q

instance (Profunctor p) => Promonad (FreePromonad p) where
  id = Unit id
  p . Unit f = lmap f p
  p . Comp r q = Comp r (p . q)
instance Functor FreePromonad where
  map = freeMap @Promonad
instance Promonad (Star FreePromonad) where
  id = Star (lift @Promonad)
  Star l . Star r = Star (freeComp @Promonad l r)
instance HasFree Promonad where
  type Free Promonad p = FreePromonad p
  lift = Prof \p -> p `Comp` Unit (tgt p)
  foldMap (Prof n) = Prof (foldFreePromonad n)

-- | The free @c@-structured kind over a @b@-structured kind @k@. A standalone family (rather than
-- an associated type of 'HasFreeK') because the kinds of 'Lift' and 'Retract' mention it.
type family FreeK (b :: Kind -> Constraint) (c :: Kind -> Constraint) (k :: Kind) :: Kind

-- | 'Proarrow.Object.Ob''-style helper: the quantified superclass of 'HasFreeK' needs to state
-- @c ('FreeK' b c k)@, and a type family application cannot head a quantified constraint directly.
class (c (FreeK b c k)) => FreeK' (b :: Kind -> Constraint) (c :: Kind -> Constraint) (k :: Kind)

instance (c (FreeK b c k)) => FreeK' b c k

-- | The embedding of an object of @k@ into the free @c@-structured kind.
type family Lift (b :: Kind -> Constraint) (c :: Kind -> Constraint) (a :: k) :: FreeK b c k

-- | Interpret an object of the free @c@-structured kind back into @k@.
type family Retract (b :: Kind -> Constraint) (c :: Kind -> Constraint) (k :: Kind) (a :: FreeK b c k) :: k

-- | @'FreeK' b c@ builds the free @c@-structured kind over any @b@-structured kind: 'liftK'
-- embeds the arrows of @k@, and when @k@ itself is already @c@-structured 'retractK' interprets
-- back into @k@.
class
  (forall k. (b k) => FreeK' b c k) =>
  HasFreeK (b :: Kind -> Constraint) (c :: Kind -> Constraint)
  where
  liftK :: (b k) => (x :: k) ~> y -> Lift b c x ~> Lift b c y
  retractK
    :: forall k (x :: FreeK b c k) (y :: FreeK b c k)
     . (c k)
    => x ~> y -> Retract b c k x ~> Retract b c k y

type instance FreeK CategoryOf Monoidal k = LIST k

type instance Lift CategoryOf Monoidal a = L '[a]
type instance Retract CategoryOf Monoidal k (a :: LIST k) = Fold (UN L a)

instance HasFreeK CategoryOf Monoidal where
  liftK f = Cons f Nil
  retractK Nil = one
  retractK (Cons f Nil) = f
  retractK (Cons f fs@Cons{}) = f ** retractK @CategoryOf @Monoidal fs

-- | The free category with a terminal object over @k@, built with
-- "Proarrow.Category.Instance.Free".
type instance FreeK CategoryOf HasTerminalObject k = FREE '[HasTerminalObject] (Hom k)

type instance Lift CategoryOf HasTerminalObject (a :: k) = EMB a
type instance Retract CategoryOf HasTerminalObject k (a :: FREE '[HasTerminalObject] (Hom k)) = Lower (Id :: CAT k) a

instance HasFreeK CategoryOf HasTerminalObject where
  liftK = liftFree
  retractK = retractFree @'[HasTerminalObject]

-- | The free category with binary products over @k@, built with
-- "Proarrow.Category.Instance.Free".
type instance FreeK CategoryOf HasBinaryProducts k = FREE '[HasBinaryProducts] (Hom k)

type instance Lift CategoryOf HasBinaryProducts (a :: k) = EMB a
type instance Retract CategoryOf HasBinaryProducts k (a :: FREE '[HasBinaryProducts] (Hom k)) = Lower (Id :: CAT k) a

instance HasFreeK CategoryOf HasBinaryProducts where
  liftK = liftFree
  retractK = retractFree @'[HasBinaryProducts]

type instance FreeK TracedMonoidal CompactClosed k = INT k

type instance Lift TracedMonoidal CompactClosed (a :: k) = I a Unit
type instance Retract TracedMonoidal CompactClosed k (I a b :: INT k) = a ** Dual b

instance HasFreeK TracedMonoidal CompactClosed where
  liftK = toInt
  retractK (Int @ap @am @bp @bm f) =
    dualObj @am //
      dualObj @bm //
        unStr $
          Str @[ap, Dual am] @[Dual am, ap] (swap @_ @ap @(Dual am)) ** Str @'[] @[bm, Dual bm] (dualityUnit @_ @bm)
            == obj @'[Dual am] ** Str @[ap, bm] @[am, bp] f ** obj @'[Dual bm]
            == Str @[Dual am, am] @'[] (dualityCounit @_ @am) ** obj @'[bp] ** obj @'[Dual bm]