packages feed

proarrow-0.1.0.0: src/Proarrow/Category/Instance/Paths.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | The category __freely generated__ by a quiver @p@: an arrow is a finite path of generators
-- ('PCons' onto 'PNil'), and an object is simply a vertex.
--
-- This is "Proarrow.Category.Instance.Free" without structures. With no formal products or
-- exponentials to form, the objects can be the vertices themselves, with 'Ob' inherited from the
-- base kind, so an object of @'PATHS' p@ can be taken apart by whatever that 'Ob' provides. An
-- object of the free structured category cannot, since @Lower@\/@lowerOb@ never yields a value
-- indexed by a shape. An empty structure list does not help: the pattern checker cannot rule out
-- the @HasStructure@ given of @'Proarrow.Category.Instance.Free.St'@, so every consumer would carry
-- an unreachable branch.
--
-- __Equations__ are supported, by 'Rewrite'. Composition only adds an arrow at the outer end of
-- the spine, so each new arrow can be normalised against an already-normal path. With the default
-- 'rewrite' the category is free.
--
-- The base kind must be a category because 'foldPaths' interprets along a functor out of it, and
-- functors are representable profunctors here. Its arrows are never used, so the intended base is
-- a discrete one.
module Proarrow.Category.Instance.Paths where

import Data.Type.Equality ((:~:) (..))
import Prelude (Eq (..), Maybe (..), Show (..), showParen, showString)
import Prelude qualified as P

import Proarrow.Category.Enriched.Thin
  ( AtOb (..)
  , Enumerable (..)
  , Finite (..)
  , FmapWrap
  , Indexed (..)
  , MapWrap
  , withWrapAtLookup
  , wrapFinite
  )
import Proarrow.Core
  ( CAT
  , CategoryOf (..)
  , Profunctor (..)
  , Promonad (..)
  , Show2
  , UN
  , WrappedOb
  , dimapDefault
  , type (+->)
  )
import Proarrow.Profunctor.Representable (Representable (..))

-- | The objects of the free category on @p@: its vertices.
type data PATHS (p :: CAT k) = PTH k

-- | A path of generators, as a right-associated spine, so that the category laws hold
-- definitionally.
type Paths :: CAT (PATHS p)
data Paths a b where
  PNil :: (Ob a) => Paths (PTH a) (PTH a)
  PCons :: (Ob a, Ob b) => p a b -> Paths (i :: PATHS p) (PTH a) -> Paths i (PTH b)

-- | The equations of the generated category, as a rewriting system on paths. 'rewrite' is handed a
-- generator and the already-normalised path it is being composed onto, and returns the normal form
-- of the two together; the default keeps the path as it is, which generates the free category.
--
-- An equation is one clause. For @Secr ⨟ WorksIn = id@, match the junction and drop both arrows:
--
-- > rewrite WorksIn (PCons Secr more) = more
--
-- The whole tail is in scope, so a longer left-hand side can be matched, and recursing on the
-- result renormalises a junction the rewrite has just exposed.
--
-- __Confluence and termination are the caller's to establish.__ Nothing here checks them, and a
-- system that lacks them breaks associativity of composition silently. Two further obligations come
-- with any non-default instance: 'foldPaths' is a functor only for interpretations that respect the
-- equations, and so is any other consumer that matches on generators.
class Rewrite (p :: CAT k) where
  rewrite :: (Ob a, Ob b) => p a b -> Paths (i :: PATHS p) (PTH a) -> Paths i (PTH b)
  rewrite = PCons

-- | Decidable equality of generators, which also has to decide their sources: the object between
-- two arrows of a path is existential, so @'Eq' (p x y)@ alone cannot compare two spines.
-- ('Proarrow.Core.Eq2' does not serve. It is equality of arrows of a fixed category, as
-- 'Proarrow.Limit.Pullback.isMono' wants.)
class EqGen (p :: CAT k) where
  eqGen :: p x b -> p y b -> Maybe (x :~: y)

-- | Structural equality of paths. This is equality of arrows exactly when 'rewrite' is confluent
-- and every path was built through 'emb', 'id' and composition, which keep paths in normal form.
-- The free /structured/ category cannot offer this: there, equality has to be decided by folding
-- both sides into some category that identifies them.
instance (EqGen p) => Eq (Paths (a :: PATHS p) b) where
  PNil == PNil = P.True
  PCons q f == PCons q' g = case eqGen q q' of
    Just Refl -> f == g
    Nothing -> P.False
  _ == _ = P.False

instance (Show2 p) => Show (Paths (a :: PATHS p) b) where
  showsPrec _ PNil = showString "id"
  showsPrec d (PCons q PNil) = showsPrec d q
  showsPrec d (PCons q f) = showParen (d P.> 9) (showsPrec 10 q . showString " . " . showsPrec 10 f)

-- | How many generators a path is made of. With a non-default 'rewrite' this is a way to see that
-- an equation fired, since a path that reduces comes out shorter.
pathLength :: Paths a b -> P.Int
pathLength PNil = 0
pathLength (PCons _ f) = 1 P.+ pathLength f

-- | A single generator, normalised. Named as in "Proarrow.Category.Instance.Free".
emb :: (Ob a, Ob b, Rewrite p) => p a b -> (PTH a :: PATHS p) ~> PTH b
emb q = rewrite q PNil

-- | Interpret a path in any category, given an interpretation of the generators: the universal
-- property of the free category. A functor out of @'PATHS' p@ is a map of vertices plus such an
-- interpretation, with nothing to check, since a quiver has no composition to preserve.
--
-- The first argument supplies @'Ob'@ of an image object. It cannot come from @f@ by
-- 'Proarrow.Profunctor.Representable.withObRep': the usual caller is @f@\'s own 'fmap', which
-- would loop on the empty path. For unconstrained objects pass @\\r -> r@.
foldPaths
  :: forall {k} {k'} {p :: CAT k} (f :: PATHS p +-> k') a b
   . (Representable f)
  => (forall x r. (Ob x) => ((Ob (f % PTH x)) => r) -> r)
  -> (forall x y. (Ob x, Ob y) => p x y -> (f % PTH x) ~> (f % PTH y))
  -> (PTH a :: PATHS p) ~> PTH b
  -> (f % PTH a) ~> (f % PTH b)
foldPaths withObF pn = go
  where
    go :: forall x y. (PTH x :: PATHS p) ~> PTH y -> (f % PTH x) ~> (f % PTH y)
    go PNil = withObF @x id
    go (PCons q g) = pn q . go g

instance (CategoryOf k, Rewrite p) => CategoryOf (PATHS (p :: CAT k)) where
  type (~>) = Paths
  type Ob a = WrappedOb PTH a

-- | Objects are vertices, so a free category has as many of them as its quiver has, however many
-- arrows the paths add (usually unboundedly many). So a free category is 'Finite' without being
-- anywhere near thin or decidable. This buys enumeration of the objects alone. That is enough for
-- @Proarrow.Testing.genSomeFinite@ to derive a schema\'s object palette, and not enough for
-- anything that wants to enumerate arrows.
instance (Indexed k) => Indexed (PATHS (p :: CAT k)) where
  type Index (a :: PATHS p) = Index (UN PTH a)
  type At (PATHS (p :: CAT k)) i = FmapWrap PTH (At k i)

instance (Finite k) => Finite (PATHS (p :: CAT k)) where
  type Objects (PATHS (p :: CAT k)) = MapWrap PTH (Objects k)
  finite = wrapFinite @PTH
  withAtLookup = withWrapAtLookup @PTH

instance (Enumerable k, Rewrite p) => Enumerable (PATHS (p :: CAT k)) where
  withIndex @(PTH a) r = withIndex @k @a r
  atOb i = case atOb @k i of
    AtJust -> AtJust
    AtNothing -> AtNothing

instance (CategoryOf k, Rewrite p) => Promonad (Paths :: CAT (PATHS (p :: CAT k))) where
  id = PNil
  PNil . g = g
  PCons q f . g = rewrite q (f . g)

instance (CategoryOf k, Rewrite p) => Profunctor (Paths :: CAT (PATHS (p :: CAT k))) where
  dimap = dimapDefault
  r \\ PNil = r
  r \\ PCons _ f = r \\ f