packages feed

proarrow-0.3.0.0: src/Proarrow/Object.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Working with objects through their identity arrows: 'Obj' @a@ is @a '~>' a@ used as a witness that
-- @a@ is an object, with 'obj', 'src' and 'tgt' to produce them and the 'Obj'\/'Objs' pattern synonyms
-- to recover 'Ob' constraints from arrows and profunctor values.
module Proarrow.Object
  ( Obj
  , pattern Obj
  , pattern Objs
  , obj
  , src
  , tgt
  , Ob'
  , VacuousOb
  , objDicts
  , ObjDict (..)

    -- * Lists of objects
  , type (++)
  , ListOf (..)
  , KnownListOf (..)
  , withKnownListOf
  , mapListOf
  , lengthListOf
  , appendListOf
  , eqListOf

    -- * One object of many
  , SomeOf (..)
  , someOfList
  , withListOf
  , someRepOf
  ) where

import Data.Kind (Constraint, Type)
import Data.Type.Equality ((:~:) (..))
import Type.Reflection (SomeTypeRep (..), Typeable, typeRep)
import Prelude (Int, (+))
import Prelude qualified as P

import Proarrow.Core (CategoryOf (..), OB, Ob', Obj, Profunctor, VacuousOb, obj, src, tgt, (\\), type (:&&:))

type ObjDict :: forall {k}. k -> Type
data ObjDict a where
  ObjDict :: (Ob a) => ObjDict a

objDicts :: (Profunctor p) => p a a' -> (ObjDict a, ObjDict a')
objDicts a = (ObjDict \\ a, ObjDict \\ a)

pattern Obj :: (CategoryOf k) => (Ob (a :: k)) => Obj a
pattern Obj <- (objDicts -> (ObjDict, ObjDict))
  where
    Obj = obj

{-# COMPLETE Obj #-}

-- | Matching a profunctor value @p a b@ against 'Objs' brings @('Ob' a, 'Ob' b)@ into scope. This
-- is the pattern form of '(\\)', handy in function equations.
pattern Objs :: (Profunctor p) => (Ob a, Ob b) => p a b
pattern Objs <- (objDicts -> (ObjDict, ObjDict))

{-# COMPLETE Objs #-}

-- | A type-level list with the evidence @c@ for each element, as a value.
type ListOf :: forall {k}. OB k -> [k] -> Type
data ListOf c xs where
  Nil :: ListOf c '[]
  Cons :: forall {k} {c :: OB k} (x :: k) xs. (c x) => ListOf c xs -> ListOf c (x ': xs)

-- | A type-level list whose elements have the evidence @c@, as its 'ListOf'.
type KnownListOf :: forall {k}. OB k -> [k] -> Constraint
class KnownListOf c xs where
  listOf :: ListOf c xs

instance KnownListOf c '[] where
  listOf = Nil
instance (c x, KnownListOf c xs) => KnownListOf c (x ': xs) where
  listOf = Cons @x listOf

-- | The class from the list.
withKnownListOf :: ListOf c xs -> ((KnownListOf c xs) => r) -> r
withKnownListOf Nil r = r
withKnownListOf (Cons rest) r = withKnownListOf rest r

-- | A value for each element, in order.
mapListOf :: forall {k} (c :: OB k) xs r. (forall (x :: k). (c x) => r) -> ListOf c xs -> [r]
mapListOf _ Nil = []
mapListOf f (Cons @x rest) = f @x : mapListOf @c (\ @y -> f @y) rest

-- | The number of elements.
lengthListOf :: ListOf c xs -> Int
lengthListOf Nil = 0
lengthListOf (Cons rest) = 1 + lengthListOf rest

-- | List concatenation.
type (++) :: [k] -> [k] -> [k]
type family as ++ bs where
  '[] ++ bs = bs
  (a ': as) ++ bs = a ': (as ++ bs)

-- | Whether two lists have the same elements, given how to decide that for one element.
eqListOf
  :: forall {k} (c :: OB k) as bs
   . (forall (x :: k) (y :: k). (c x, c y) => P.Maybe (x :~: y))
  -> ListOf c as
  -> ListOf c bs
  -> P.Maybe (as :~: bs)
eqListOf _ Nil Nil = P.Just Refl
eqListOf eq (Cons @x xs) (Cons @y ys) = case (eq @x @y, eqListOf @c eq xs ys) of
  (P.Just Refl, P.Just Refl) -> P.Just Refl
  _ -> P.Nothing
eqListOf _ _ _ = P.Nothing

-- | The elements of both lists.
appendListOf :: ListOf c as -> ListOf c bs -> ListOf c (as ++ bs)
appendListOf Nil ys = ys
appendListOf (Cons @x xs) ys = Cons @x (appendListOf xs ys)

-- | Some type with the evidence @c@, which one known only at runtime.
type SomeOf :: forall {k}. OB k -> Type
data SomeOf c where
  Some :: forall {k} {c :: OB k} (x :: k). (c x) => SomeOf c

-- | The elements of the list, each on its own.
someOfList :: forall {k} (c :: OB k) xs. ListOf c xs -> [SomeOf c]
someOfList = mapListOf (\ @x -> Some @x)

-- | A list of types known at runtime as a type-level list.
withListOf :: forall {k} (c :: OB k) r. [SomeOf c] -> (forall (xs :: [k]). ListOf c xs -> r) -> r
withListOf [] k = k Nil
withListOf (Some @x : rest) k = withListOf rest (\l -> k (Cons @x l))

-- | The type representation of the type, which is what 'SomeOf' values are compared and shown by.
someRepOf :: SomeOf (Typeable :&&: c) -> SomeTypeRep
someRepOf (Some @x) = SomeTypeRep (typeRep @x)

instance P.Eq (SomeOf (Typeable :&&: c)) where
  x == y = someRepOf x P.== someRepOf y
instance P.Ord (SomeOf (Typeable :&&: c)) where
  compare x y = P.compare (someRepOf x) (someRepOf y)
instance P.Show (SomeOf (Typeable :&&: c)) where
  show = P.show P.. someRepOf