circuits-0.2.0.0: src/Circuit/Category.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Local category hierarchy with object constraints.
--
-- 'Category' is local so morphisms can carry an associated 'Ob'
-- constraint.
--
-- 'Discrete' marks categories whose 'Ob' is trivial for every object
-- (@Ob = ()@). Constrained bases can implement 'Circuit.Loop.Traced'
-- directly, but /hosting/ the free constructions (@Free@, @Sym@, @Loop@,
-- @Net@, and the syntax in "Circuit.Algebra") additionally requires
-- 'Discrete', because compound tensor objects (e.g. @t s a@ inside a
-- 'Loop.Knot') carry no 'Ob' evidence and must be manufactured on demand.
--
-- == Operator convention
--
-- The tip of the operator points in the direction of data flow.
--
-- * @|@ means /apply/ to a value: @('|>')@ feeds a value into a function
-- (forward application, like @&@), and @('<|')@ applies a function
-- to a value (backward application, like @('$')@).
-- * @.@ means /compose/ morphisms: @('.>')@ is forward composition and
-- @('.')@ is backward composition as usual.
module Circuit.Category
( Category (..),
Discrete (..),
(.>),
(|>),
(<|),
)
where
import Control.Arrow (Kleisli (..))
import Control.Monad ((<=<))
import Data.Kind (Constraint, Type)
import Prelude hiding (id, (.))
-- | A category whose objects may carry a constraint.
--
-- @Ob arr a@ is the evidence required to mention object @a@ in @arr@.
-- Unconstrained categories use the default @()@. Constrained categories
-- specialise the associated type to whatever their objects require.
class Category (arr :: k -> k -> Type) where
-- | Object constraint for this category.
type Ob arr (a :: k) :: Constraint
type Ob arr a = ()
-- | Identity morphism.
id :: (Ob arr a) => arr a a
-- | Composition (right-to-left).
(.) :: (Ob arr a, Ob arr b, Ob arr c) => arr b c -> arr a b -> arr a c
-- | Categories with a trivial object constraint for every type.
--
-- 'withOb' discharges @Ob arr a@ at an arbitrary @a@. Free constructions
-- that bind an existential object (notably @Loop@ in "Circuit.Loop")
-- use it where a polymorphic @Ob@ constraint cannot be written.
class (Category arr) => Discrete arr where
withOb :: forall a r. ((Ob arr a) => r) -> r
-- | Forward composition. @f .> g = g . f@
(.>) :: (Category arr, Ob arr a, Ob arr b, Ob arr c) => arr a b -> arr b c -> arr a c
f .> g = g . f
{-# INLINE (.>) #-}
-- | Forward application. @x |> f = f x@
(|>) :: a -> (a -> b) -> b
x |> f = f x
{-# INLINE (|>) #-}
infixl 1 |>
-- | Backward application. @f <| x = f x@
(<|) :: (a -> b) -> a -> b
f <| x = f x
{-# INLINE (<|) #-}
infixr 0 <|
-- | Unconstrained function category.
instance Category (->) where
type Ob (->) a = ()
id x = x
(f . g) x = f (g x)
instance Discrete (->) where
withOb x = x
-- | Kleisli arrows of a monad (unconstrained objects).
instance (Monad m) => Category (Kleisli m) where
type Ob (Kleisli m) a = ()
id = Kleisli pure
Kleisli f . Kleisli g = Kleisli (f <=< g)
instance (Monad m) => Discrete (Kleisli m) where
withOb x = x