packages feed

proarrow-0.1.0.0: src/Proarrow/Category/Enriched/Thin.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Thin categories, where any two parallel arrows are equal: a 'ThinProfunctor' has at most one
-- element between any two objects, mere existence being captured by the constraint
-- @'HasArrow' p a b@. Also defines the codiscrete (always exactly one arrow) and discrete (only
-- identity arrows) special cases; 'DecidableProfunctor's, whose arrows are computed at the type
-- level as a 'BOOL' (the category thin profunctors are enriched in); and 'Indexed', 'Finite' and
-- 'Enumerable' kinds and categories, whose inhabitants are numbered, listed, and reflected to the
-- value level.
module Proarrow.Category.Enriched.Thin where

import Data.Kind (Constraint, Type)
import Data.Type.Equality (type (:~:) (..))
import Data.Type.Nat (Nat (..), SNat (..), SNatI, snat)
import Prelude (Maybe (..), type (~))

import Proarrow.Category.Instance.Bool (BOOL (..), BoolLeq, Booleans (..), NonTrivialHolds, NonTrivialProfunctor (..))
import Proarrow.Category.Instance.Zero (Bottom (..), VOID, Zero)
import Proarrow.Core (CAT, CategoryOf (..), Hom, Kind, Profunctor (..), VacuousOb, obj, type (+->))

-- | The defaults take everything from a 'DecidableProfunctor' instance: the arrow exists when
-- @'Holds' p a b@ computes to 'TRU'.
type ThinProfunctor :: forall {j} {k}. j +-> k -> Constraint
class (Profunctor p) => ThinProfunctor (p :: j +-> k) where
  type HasArrow (p :: j +-> k) (a :: k) (b :: j) :: Constraint
  type HasArrow p a b = Holds p a b ~ TRU
  arr :: (Ob a, Ob b, HasArrow p a b) => p a b
  default arr :: (Ob a, Ob b, DecidableProfunctor p, Holds p a b ~ TRU) => p a b
  arr = fromHolds
  withArr :: p a b -> ((HasArrow p a b, Ob a, Ob b) => r) -> r
  default withArr
    :: (DecidableProfunctor p, HasArrow p a b ~ (Holds p a b ~ TRU)) => p a b -> ((HasArrow p a b, Ob a, Ob b) => r) -> r
  withArr = toHolds

instance ThinProfunctor Zero
instance ThinProfunctor Booleans
instance (Ob ff, Ob tt) => ThinProfunctor (NonTrivialProfunctor '(ff, tt))

instance (VacuousOb k, Hom k ~ (:~:)) => ThinProfunctor ((:~:) :: CAT k) where
  type HasArrow ((:~:) :: CAT k) a b = a ~ b
  arr = Refl
  withArr Refl r = r

-- * Decidable thin profunctors

-- | The value-level shadow of a type-level 'BOOL' @h@ answering whether @p a b@ has an arrow: the
-- arrow itself when @h@ is 'TRU', nothing when it is 'FLS'.
type Decision :: forall {j} {k}. (j +-> k) -> k -> j -> BOOL -> Type
data Decision p a b h where
  Yes :: p a b -> Decision p a b TRU
  No :: Decision p a b FLS

mapDecision :: (p a b -> q c d) -> Decision p a b h -> Decision q c d h
mapDecision f (Yes x) = Yes (f x)
mapDecision _ No = No

-- | A thin profunctor whose arrows are decidable at the type level: @'Holds' p a b@ is the
-- 'BOOL'-valued profunctor a thin profunctor really is, computed by a type family, so it reduces to
-- 'TRU' or 'FLS' for concrete objects. It agrees with 'HasArrow' ('fromHolds' and 'toHolds' are the
-- two directions of that agreement, and the 'ThinProfunctor' defaults make it definitional), and
-- 'decide' computes the answer at the value level, arrow included. With it a composite of thin
-- profunctors can search for its middle object ("Proarrow.Category.Enriched.Thin.Composition").
type DecidableProfunctor :: forall {j} {k}. j +-> k -> Constraint
class (ThinProfunctor p) => DecidableProfunctor (p :: j +-> k) where
  type Holds (p :: j +-> k) (a :: k) (b :: j) :: BOOL
  decide :: (Ob a, Ob b) => Decision p a b (Holds p a b)
  toHolds :: p a b -> ((Holds p a b ~ TRU, Ob a, Ob b) => r) -> r

fromHolds :: forall {j} {k} (p :: j +-> k) a b. (DecidableProfunctor p, Ob a, Ob b, Holds p a b ~ TRU) => p a b
fromHolds = case decide @p @a @b of Yes x -> x

-- | A profunctor that decides against an arrow has none, so a caller holding one may return anything.
noArrow :: forall {j} {k} (p :: j +-> k) a b r. (DecidableProfunctor p, Holds p a b ~ FLS) => p a b -> r
noArrow x = case eq of {}
  where
    eq :: Holds p a b :~: TRU
    eq = toHolds x Refl

-- | A thin category whose order is decidable at the type level.
class (DecidableProfunctor (Hom k), CategoryOf k) => Decidable k

instance (DecidableProfunctor (Hom k), CategoryOf k) => Decidable k

instance DecidableProfunctor Zero where
  type Holds Zero a b = FLS
  decide = no
  toHolds = \case {}

instance DecidableProfunctor Booleans where
  type Holds Booleans a b = BoolLeq a b
  decide @a @b = case (obj @a, obj @b) of
    (Fls, Fls) -> Yes Fls
    (Fls, Tru) -> Yes F2T
    (Tru, Tru) -> Yes Tru
    (Tru, Fls) -> No
  toHolds Fls r = r
  toHolds F2T r = r
  toHolds Tru r = r

instance (Ob ff, Ob tt) => DecidableProfunctor (NonTrivialProfunctor '(ff, tt)) where
  type Holds (NonTrivialProfunctor '(ff, tt)) a b = NonTrivialHolds ff tt a b
  decide @a @b = case (obj @a, obj @b) of
    (Fls, Fls) -> case obj @ff of
      Fls -> No
      Tru -> Yes FF
    (Fls, Tru) -> Yes FT
    (Tru, Tru) -> case obj @tt of
      Fls -> No
      Tru -> Yes TT
    (Tru, Fls) -> No
  toHolds FF r = r
  toHolds FT r = r
  toHolds TT r = r

class (ThinProfunctor (Hom k), CategoryOf k) => Thin k
instance (ThinProfunctor (Hom k), CategoryOf k) => Thin k

class (ThinProfunctor p, Ob a, Ob b, HasArrow p a b) => HasArrow' p a b where arr' :: p a b
instance (ThinProfunctor p, Ob a, Ob b, HasArrow p a b) => HasArrow' p a b where arr' = arr

type CodiscreteProfunctor :: forall {j} {k}. j +-> k -> Constraint
class
  (ThinProfunctor p, forall c d. (Ob c, Ob d) => HasArrow' p c d, Codiscrete j, Codiscrete k) =>
  CodiscreteProfunctor (p :: j +-> k)
  where
  anyArr :: (Ob a, Ob b) => p a b
instance
  (ThinProfunctor p, forall c d. (Ob c, Ob d) => HasArrow' p c d, Codiscrete j, Codiscrete k)
  => CodiscreteProfunctor (p :: j +-> k)
  where
  anyArr = arr'

type Codiscrete k = CodiscreteProfunctor (Hom k)

class ((c) => d, (d) => c) => c <=> d
instance ((c) => d, (d) => c) => c <=> d

class ((HasArrow p a b) => Bottom) => HasNoArrow p a b where
  arrowIsBottomProof :: (HasArrow p a b) => r
instance ((HasArrow p a b) => Bottom) => HasNoArrow p a b where
  arrowIsBottomProof = no

type DiscreteProfunctor :: forall {j} {k}. j +-> k -> Constraint
class (ThinProfunctor p, forall a b. (Ob a, Ob b) => HasNoArrow p a b) => DiscreteProfunctor (p :: j +-> k) where
  exfalso :: p a b -> r
instance (ThinProfunctor p, forall a b. (Ob a, Ob b) => HasNoArrow p a b) => DiscreteProfunctor (p :: j +-> k) where
  exfalso @a @b p = withArr p (arrowIsBottomProof @p @a @b)

class ((HasArrow (Hom k) c d) <=> (c ~ d)) => ArrowIsId k c d where
  arrowIsIdProof :: (HasArrow (Hom k) c d) => ((c ~ d) => r) -> r
instance ((HasArrow (Hom k) c d) <=> (c ~ d)) => ArrowIsId k c d where
  arrowIsIdProof r = r

-- | @Discrete k@ is not the same as @DiscreteProfunctor (Hom k)@!
class (Thin k, forall c d. (Ob c, Ob d) => ArrowIsId k c d) => Discrete k where
  withEq :: (a :: k) ~> b -> ((a ~ b) => r) -> r

instance (Thin k, forall c d. (Ob c, Ob d) => ArrowIsId k c d) => Discrete k where
  withEq @a @b f r = withArr f (arrowIsIdProof @k @a @b r)

-- * Indexed, finite and enumerable kinds

-- | A kind whose inhabitants are numbered: 'Index' gives each its position and 'At' reads it back,
-- so that two inhabitants are equal exactly when their indices are ('decideEq'). 'At' is partial, so
-- that finitely many inhabitants can be numbered by an initial segment of the naturals.
class Indexed k where
  type Index (a :: k) :: Nat

  -- | A 'Finite' kind is numbered by its own object list: this default and the one for 'At' are
  -- inverse walks of 'Objects', so an instance that lists its inhabitants need say nothing here.
  type Index (a :: k) = IndexOf a (Objects k)

  type At k (i :: Nat) :: Maybe k
  type At k i = Lookup (Objects k) i

-- | The evidence that @a@ is numbered: its index, reflected, and 'At' reading it back.
class (SNatI (Index a), At k (Index a) ~ 'Just a) => KnownIndex (a :: k)

instance (SNatI (Index a), At k (Index a) ~ 'Just a) => KnownIndex (a :: k)

instance Indexed Nat where
  type Index n = n
  type At Nat i = 'Just i

-- | Equality of naturals, with evidence either way.
type NatEq :: Nat -> Nat -> BOOL
type family NatEq n m where
  NatEq 'Z 'Z = TRU
  NatEq ('S n) ('S m) = NatEq n m
  NatEq n m = FLS

natEq :: SNat n -> SNat m -> Decision (:~:) n m (NatEq n m)
natEq SZ SZ = Yes Refl
natEq (SS @n) (SS @m) = mapDecision (\Refl -> Refl) (natEq (snat @n) (snat @m))
natEq SZ SS = No
natEq SS SZ = No

withNatEqRefl :: forall n r. SNat n -> ((NatEq n n ~ TRU) => r) -> r
withNatEqRefl SZ r = r
withNatEqRefl (SS @n') r = withNatEqRefl (snat @n') r

-- | Two numbered inhabitants are equal exactly when their indices are.
type Equal (a :: k) (b :: k) = NatEq (Index a) (Index b)

decideEq :: forall {k} (a :: k) b. (KnownIndex a, KnownIndex b) => Decision (:~:) a b (Equal a b)
decideEq = case natEq (snat @(Index a)) (snat @(Index b)) of
  Yes Refl -> Yes Refl
  No -> No

type Length :: [k] -> Nat
type family Length xs where
  Length '[] = 'Z
  Length (x ': xs) = 'S (Length xs)

type Lookup :: [k] -> Nat -> Maybe k
type family Lookup xs i where
  Lookup '[] i = 'Nothing
  Lookup (x ': xs) 'Z = 'Just x
  Lookup (x ': xs) ('S i) = Lookup xs i

-- | The inhabitant at an index in a type-level list known to be long enough: 'Lookup' without the
-- 'Maybe', for tables indexed by 'Index'. Out of range it is stuck rather than 'Nothing'.
type Entry :: [k] -> Nat -> k
type family Entry xs i where
  Entry (x ': xs) 'Z = x
  Entry (x ': xs) ('S i) = Entry xs i

-- | Every entry of @xs@ satisfies @c@. The list is shaped like @shape@, a list of objects, so that
-- an index into @shape@ selects an entry of @xs@. The equality argument ties the index to @shape@,
-- so that walking off the end is refutable rather than an error.
type KnownList :: forall {x} {y}. (x -> Constraint) -> [y] -> [x] -> Constraint
class KnownList c shape xs where
  withEntry :: forall s i r. SNat i -> Lookup shape i :~: 'Just s -> ((c (Entry xs i)) => r) -> r

instance KnownList c '[] '[] where
  withEntry _ eq _ = case eq of {}

instance (c x, KnownList c shape xs) => KnownList c (s ': shape) (x ': xs) where
  withEntry SZ Refl r = r
  withEntry (SS @i') eq r = withEntry @c @shape @xs (snat @i') eq r

-- | Where an inhabitant sits in a type-level list, the inverse of 'Lookup'. An inhabitant that does
-- not occur has no index, so the family is stuck rather than total.
type IndexOf :: forall k. k -> [k] -> Nat
type family IndexOf a xs where
  IndexOf a (a ': xs) = 'Z
  IndexOf a (b ': xs) = 'S (IndexOf a xs)

-- | A type-level list of inhabitants, reflected to the value level with their indices.
type IndexedList :: forall k. [k] -> Type
data IndexedList as where
  FNil :: IndexedList '[]
  FCons :: forall a as. (KnownIndex a) => IndexedList as -> IndexedList (a ': as)

-- | Every element of the list is numbered, so the list can be reflected to an 'IndexedList'. So a
-- kind that simply writes its objects out gets 'finite' for free.
class HasFiniteDefault (xs :: [k]) where
  finiteDefault :: IndexedList xs

instance HasFiniteDefault '[] where
  finiteDefault = FNil
instance (KnownIndex a, HasFiniteDefault as) => HasFiniteDefault (a ': as) where
  finiteDefault = FCons finiteDefault

-- | An 'Indexed' kind with finitely many inhabitants, listed in 'Objects' in the order of their
-- indices: 'withAtLookup' says that the list tabulates 'At'.
class (Indexed k) => Finite k where
  type Objects k :: [k]
  finite :: IndexedList (Objects k)
  default finite :: (HasFiniteDefault (Objects k)) => IndexedList (Objects k)
  finite = finiteDefault
  withAtLookup :: forall (i :: Nat) r. SNat i -> ((Lookup (Objects k) i ~ At k i) => r) -> r
  default withAtLookup
    :: forall (i :: Nat) r. (At k i ~ Lookup (Objects k) i) => SNat i -> ((Lookup (Objects k) i ~ At k i) => r) -> r
  withAtLookup _ r = r

-- | A proof that @a@ occurs in the type-level list @as@.
type Member :: forall k. k -> [k] -> Type
data Member a as where
  Here :: Member a (a ': as)
  There :: Member a as -> Member a (b ': as)

-- | Every numbered inhabitant of a finite kind occurs in its list: walk to its index.
memberIndex :: forall {k} (a :: k). (Finite k, KnownIndex a) => Member a (Objects k)
memberIndex = withAtLookup @k (snat @(Index a)) (go (snat @(Index a)) (finite @k))
  where
    go :: forall i xs. (Lookup xs i ~ 'Just a) => SNat i -> IndexedList xs -> Member a xs
    go SZ (FCons _) = Here
    go (SS @i') (FCons xs) = There (go (snat @i') xs)

-- | A category on a 'Finite' kind whose objects are exactly its numbered inhabitants: 'withIndex'
-- and 'withOb' convert between the two notions, and 'atOb' looks an object up by its index.
type Enumerable :: Kind -> Constraint
class (CategoryOf k, Finite k) => Enumerable k where
  withIndex :: forall (a :: k) r. (Ob a) => ((KnownIndex a) => r) -> r
  withOb :: forall (a :: k) r. (KnownIndex a) => ((Ob a) => r) -> r
  withOb @x r = case atOb @k (snat @(Index x)) of AtJust -> r

  -- | The object at an index, if there is one. The default walks the object list, which is all a
  -- kind in general can do. A kind that can answer from the index alone should say so, and a wrapper
  -- kind whose base is itself 'Enumerable' should defer to it. The discrete kinds cannot, since
  -- they ask only that the kind they wrap be 'Finite'.
  atOb :: forall (i :: Nat). SNat i -> AtOb k (At k i)
  atOb i = withAtLookup @k i (lookupOb @k i (finite @k))

  {-# MINIMAL withIndex, (atOb | withOb) #-}

-- | Locate an object in the object list.
member :: forall {k} (a :: k). (Enumerable k, Ob a) => Member a (Objects k)
member = withIndex @k @a (memberIndex @a)

-- | Whether the inhabitant at an index exists, and if so that it is an object. Indexed by the lookup
-- itself, so that a caller holding @'At' k i ~ ''Just' a@ learns @'Ob' a@. A wrapper kind needs
-- this to recover the objects of the kind it wraps.
type AtOb :: forall k -> Maybe k -> Type
data AtOb k x where
  AtNothing :: AtOb k 'Nothing
  AtJust :: (Ob a, KnownIndex a) => AtOb k ('Just a)

-- | A numbered inhabitant is found at its own index, so evidence that nothing is there refutes
-- itself: under @'KnownIndex' a@ the argument's type is @''Just' a ':~:' ''Nothing'@, and a caller
-- holding one may return anything.
noIndex :: forall {k} (a :: k) r. (KnownIndex a) => At k (Index a) :~: 'Nothing -> r
noIndex eq = case eq of {}

-- | A kind that wraps another, one inhabitant for one, keeps its numbering: map the wrapper over the
-- lookup ('FmapWrap') and over the object list ('MapWrap'), and the two agree ('withLookupMapWrap').
type FmapWrap :: forall {j} {k}. (j -> k) -> Maybe j -> Maybe k
type family FmapWrap w x where
  FmapWrap w 'Nothing = 'Nothing
  FmapWrap w ('Just a) = 'Just (w a)

type MapWrap :: forall {j} {k}. (j -> k) -> [j] -> [k]
type family MapWrap w xs where
  MapWrap w '[] = '[]
  MapWrap w (x ': xs) = w x ': MapWrap w xs

mapWrap
  :: forall {j} {k} (w :: j -> k) xs
   . (forall (a :: j). (KnownIndex a) => KnownIndex (w a))
  => IndexedList xs -> IndexedList (MapWrap w xs)
mapWrap FNil = FNil
mapWrap (FCons @a xs) = FCons @(w a) (mapWrap @w xs)

withLookupMapWrap
  :: forall {j} {k} (w :: j -> k) xs i r
   . SNat i -> IndexedList xs -> ((Lookup (MapWrap w xs) i ~ FmapWrap w (Lookup xs i)) => r) -> r
withLookupMapWrap _ FNil r = r
withLookupMapWrap SZ (FCons _) r = r
withLookupMapWrap (SS @i') (FCons xs) r = withLookupMapWrap @w (snat @i') xs r

-- | The two 'Finite' methods of a wrapper kind, which are the same for every wrapper.
wrapFinite
  :: forall {j} {k} (w :: j -> k)
   . (Finite j, forall (a :: j). (KnownIndex a) => KnownIndex (w a))
  => IndexedList (MapWrap w (Objects j))
wrapFinite = mapWrap @w (finite @j)

withWrapAtLookup
  :: forall {j} {k} (w :: j -> k) i r
   . (Finite j)
  => SNat i -> ((Lookup (MapWrap w (Objects j)) i ~ FmapWrap w (At j i)) => r) -> r
withWrapAtLookup i r = withAtLookup @j i (withLookupMapWrap @w i (finite @j) r)

-- | The default 'atOb': walk the object list to the index.
lookupOb :: forall k (j :: Nat) xs. (Enumerable k) => SNat j -> IndexedList (xs :: [k]) -> AtOb k (Lookup xs j)
lookupOb _ FNil = AtNothing
lookupOb SZ (FCons @a _) = withOb @k @a AtJust
lookupOb (SS @j') (FCons xs) = lookupOb @k (snat @j') xs

instance Indexed BOOL

instance Finite BOOL where type Objects BOOL = '[FLS, TRU]

instance Enumerable BOOL where
  withIndex @a r = case obj @a of
    Fls -> r
    Tru -> r
  withOb @a r = case snat @(Index a) of
    SZ -> r
    SS @i -> case snat @i of SZ -> r

-- | The empty kind has no inhabitants to number.
instance Indexed VOID where
  type Index (a :: VOID) = 'Z
  type At VOID i = 'Nothing

instance Finite VOID where type Objects VOID = '[]

instance Enumerable VOID where
  withIndex _ = no
  withOb @a _ = noIndex @a Refl