packages feed

proarrow-0.1.0.0: src/Proarrow/Limit.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Profunctor-weighted limits: @'HasLimits' j k@ says @k@ has limits of @i '+->' k@-diagrams weighted
-- by @j@, given by the 'Limit' profunctor with 'limit' and 'limitUniv'. The
-- 'Proarrow.Profunctor.Instance.Terminal.TerminalProfunctor' weight recovers ordinary conical
-- limits, e.g. terminal objects, binary products and powers as special shapes.
--
-- The helpers that state those instances (@Unweighted@, @O1@\/@O2@, @At1@\/@At2@, @Hom@, @Ran@) are
-- not exported, since they clash with names in "Proarrow.Colimit", "Proarrow.Core" and
-- "Proarrow.Profunctor.Instance.Ran".
module Proarrow.Limit
  ( HasLimits (..)
  , IsRepresentableLimit
  , mapLimit
  , ProductLimit
  , PowerLimit
  , End (..)
  , EndLimit
  , AnyLimit (..)
  ) where

import Data.Function (($))
import Data.Kind (Constraint, Type)

import Proarrow.Category.Instance.Coproduct (COPRODUCT, IsLR (..), L, R)
import Proarrow.Category.Instance.Opposite (OPPOSITE (..), Op (..))
import Proarrow.Category.Instance.Product ((:**:) (..))
import Proarrow.Category.Instance.Prof (Prof (..))
import Proarrow.Category.Instance.Unit (Unit (..))
import Proarrow.Category.Instance.Zero (VOID)
import Proarrow.Core (CAT, CategoryOf (..), Kind, Profunctor (..), Promonad (..), rmap, (//), (:~>), type (+->))
import Proarrow.Functor (Copresheaf, Functor (..), FunctorForRep (..), Presheaf)
import Proarrow.Limit.BinaryProduct (HasBinaryProducts (..), fst, snd)
import Proarrow.Limit.Power (Powered (..))
import Proarrow.Limit.Terminal (HasTerminalObject (..), terminate)
import Proarrow.Profunctor.Corepresentable (Corep (..), corepUniv)
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Instance.Constant (Constant)
import Proarrow.Profunctor.Instance.HaskValue (HaskValue (..))
import Proarrow.Profunctor.Instance.Identity (Id (..))
import Proarrow.Profunctor.Instance.Star (Star, pattern Star)
import Proarrow.Profunctor.Instance.Terminal (TerminalProfunctor (..))
import Proarrow.Profunctor.Representable (Rep (..), Representable (..), repUniv, withObRep)

class (Representable (Limit j d)) => IsRepresentableLimit j d
instance (Representable (Limit j d)) => IsRepresentableLimit j d

-- | profunctor-weighted limits
type HasLimits :: forall {a} {i}. i +-> a -> Kind -> Constraint
class (Profunctor j, forall (d :: i +-> k). (Representable d) => IsRepresentableLimit j d) => HasLimits (j :: i +-> a) k where
  type Limit (j :: i +-> a) (d :: i +-> k) :: a +-> k
  limit :: (Representable (d :: i +-> k)) => Limit j d :.: j :~> d
  limitUniv :: (Representable (d :: i +-> k), Profunctor p) => p :.: j :~> d -> p :~> Limit j d

mapLimit
  :: forall {i} j k p q. (HasLimits j k, Representable p, Representable q) => (p :: i +-> k) ~> q -> Limit j p ~> Limit j q
mapLimit (Prof n) = Prof (limitUniv @j (n . limit @j))

type Unweighted = TerminalProfunctor

instance (HasTerminalObject k) => HasLimits (Unweighted :: Copresheaf VOID) k where
  type Limit Unweighted d = Rep (Constant TerminalObject)
  limit (_ :.: t) = case t of {}
  limitUniv _ p = p // Rep terminate

type O1 = L '()
type O2 = R '()
type At1 d = d % O1
type At2 d = d % O2

data family ProductLimit :: COPRODUCT () () +-> k -> Presheaf k
instance (HasBinaryProducts k, Representable d) => FunctorForRep (ProductLimit d :: Presheaf k) where
  type ProductLimit d @ '() = At1 d && At2 d
  fmap Unit = withObRep @d @O1 $ withObRep @d @O2 $ withObProd @_ @(At1 d) @(At2 d) id

instance (HasBinaryProducts k) => HasLimits (Unweighted :: Copresheaf (COPRODUCT () ())) k where
  type Limit Unweighted d = Rep (ProductLimit d)
  limit @d (Rep f :.: TerminalProfunctor @_ @o) =
    withObRep @d @O1 $
      withObRep @d @O2 $
        lrCase @o
          (tabulate (fst @_ @(At1 d) @(At2 d) . f))
          (tabulate (snd @_ @(At1 d) @(At2 d) . f))
  limitUniv n p = p // Rep (index (n (p :.: TerminalProfunctor @'() @O1)) &&& index (n (p :.: TerminalProfunctor @'() @O2)))

data family PowerLimit :: v -> Presheaf k -> Presheaf k
instance (Representable d, Powered v k, Ob n) => FunctorForRep (PowerLimit (n :: v) d :: Presheaf k) where
  type PowerLimit n d @ '() = (d % '()) ^ n
  fmap Unit = withObRep @d @'() $ withObPower @v @k @(d % '()) @n id
instance (Powered Type k) => HasLimits (HaskValue n :: Copresheaf ()) k where
  type Limit (HaskValue n) d = Rep (PowerLimit n d)
  limit @d (Rep f :.: HaskValue n) = withObRep @d @'() $ tabulate (unpower f n)
  limitUniv @d m p = withObRep @d @'() $ Rep (power \n -> index (m (p :.: HaskValue n))) \\ p

newtype End d = End {unEnd :: forall a b. a ~> b -> d % '(OP a, b)}

data family EndLimit :: (OPPOSITE k, k) +-> Type -> Presheaf Type
instance (Representable d) => FunctorForRep (EndLimit (d :: (OPPOSITE k, k) +-> Type)) where
  type EndLimit d @ '() = End d
  fmap Unit = id

-- | The hom-functor of @k@ as a weight: the limit of a diagram @('OPPOSITE' k, k) '+->' ()@
-- weighted by 'Hom' is its end.
type Hom :: Copresheaf (OPPOSITE k, k)
data Hom a b where
  Hom :: a ~> b -> Hom '() '(OP a, b)

instance (CategoryOf k) => Profunctor (Hom :: Copresheaf (OPPOSITE k, k)) where
  dimap Unit (Op l :**: r) (Hom f) = Hom (r . f . l) \\ l \\ r
  r \\ Hom f = r \\ f

instance (CategoryOf k) => HasLimits (Hom :: Copresheaf (OPPOSITE k, k)) Type where
  type Limit Hom d = Rep (EndLimit d)
  limit (Rep f :.: Hom k) = k // tabulate (\a -> unEnd (f a) k)
  limitUniv n p = p // Rep \a -> End \x -> index (n (p :.: Hom x)) a

instance (CategoryOf j) => HasLimits (Id :: CAT j) k where
  type Limit Id d = d
  limit (d :.: Id f) = rmap f d
  limitUniv n p = n (p :.: Id id) \\ p

instance (Representable j1, HasLimits j1 k, HasLimits j2 k) => HasLimits (j1 :.: j2) k where
  type Limit (j1 :.: j2) d = Limit j1 (Limit j2 d)
  limit @d (l :.: (j1 :.: j2)) = limit @j2 @k @d (limit @j1 @k @(Limit j2 d) (l :.: j1) :.: j2)
  limitUniv @d n = limitUniv @j1 @k @(Limit j2 d) (limitUniv @j2 @k @d (\((p' :.: j1) :.: j2) -> n (p' :.: (j1 :.: j2))))

instance (FunctorForRep f) => HasLimits (Corep f) k where
  type Limit (Corep f) d = d :.: Rep f
  limit ((d :.: Rep f) :.: Corep g) = rmap (g . f) d
  limitUniv n p = p // n (p :.: corepUniv) :.: repUniv

newtype AnyLimit j a b = AnyLimit (j a b)
  deriving newtype (Profunctor)
type Ran :: (i +-> a) -> (i +-> Type) -> a -> Type
newtype Ran j d a = Ran {runRan :: forall b. j a b -> d % b}
instance (Profunctor j, Representable d) => Functor (Ran j d) where
  map f (Ran g) = Ran \j -> g (lmap f j)
instance (Profunctor j) => HasLimits (AnyLimit j) Type where
  type Limit (AnyLimit j) d = Star (Ran j d)
  limit (Star f :.: AnyLimit j) = tabulate (\a -> runRan (f a) j) \\ j
  limitUniv n p = p // Star (\a -> Ran \j -> index (n (p :.: AnyLimit j)) a)