proarrow-0.1.0.0: src/Proarrow/Category/Instance/PointedHask.hs
-- | The category of __pointed types__: objects are Haskell types with an added point (wrapped in
-- 'P'), and a morphism is a point-preserving function, represented as @a -> Maybe b@ ('Pt'). The
-- binary product is 'These' (each component present or the point) with @Void@ as terminal object,
-- and the coproduct identifies the two points (a wedge sum).
module Proarrow.Category.Instance.PointedHask where
import Control.Monad ((>=>))
import Data.Kind (Type)
import Data.Map.Lazy qualified as Map
import Data.Map.Merge.Lazy qualified as Map
import Data.Maybe qualified as P
import Data.Void (Void, absurd)
import GHC.Generics (Generic)
import Prelude (Eq, Maybe (..), Ord, Show, const, ($), (>>=), type (~))
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..))
import Proarrow.Category.Monoidal.Applicative (Applicative (..))
import Proarrow.Category.Monoidal.CopyDiscard (CopyDiscard)
import Proarrow.Colimit.BinaryCoproduct (HasBinaryCoproducts (..))
import Proarrow.Colimit.Copower (Copowered (..))
import Proarrow.Colimit.Initial (HasInitialObject (..), HasZeroObject (..))
import Proarrow.Core (CAT, CategoryOf (..), Profunctor (..), Promonad (..), UN, dimapDefault)
import Proarrow.Functor (Functor (..))
import Proarrow.Limit.BinaryProduct (FromProd (..), HasBinaryProducts (..), Prod (..))
import Proarrow.Limit.Power (Powered (..))
import Proarrow.Limit.Terminal (HasTerminalObject (..))
import Proarrow.Monoid (CocommutativeComonoid, Comonoid (..), Monoid (..))
type data POINTED = P Type
type Pointed :: CAT POINTED
data Pointed a b where
Pt :: {unPt :: a -> Maybe b} -> Pointed (P a) (P b)
toHask :: P a ~> P b -> (Maybe a -> Maybe b)
toHask (Pt f) = (>>= f)
instance Profunctor Pointed where
dimap = dimapDefault
r \\ Pt{} = r
instance Promonad Pointed where
id = Pt Just
Pt f . Pt g = Pt (g >=> f)
-- | The category of types with an added point and point-preserving morphisms.
instance CategoryOf POINTED where
type (~>) = Pointed
type Ob a = (a ~ P (UN P a))
data These a b = This a | That b | These a b
deriving (Eq, Show, Generic)
instance HasBinaryProducts POINTED where
type P a && P b = P (These a b)
withObProd r = r
fst = Pt (\case This a -> Just a; That _ -> Nothing; These a _ -> Just a)
snd = Pt (\case This _ -> Nothing; That b -> Just b; These _ b -> Just b)
Pt f &&& Pt g =
Pt
( \a -> case (f a, g a) of
(Just a', Just b') -> Just (These a' b')
(Just a', Nothing) -> Just (This a')
(Nothing, Just b') -> Just (That b')
(Nothing, Nothing) -> Nothing
)
instance HasTerminalObject POINTED where
type TerminalObject = P Void
terminate = Pt (const Nothing)
instance HasBinaryCoproducts POINTED where
type P a || P b = P (a || b)
withObCoprod r = r
lft = Pt (Just . lft)
rgt = Pt (Just . rgt)
Pt f ||| Pt g = Pt (f ||| g)
instance HasInitialObject POINTED where
type InitialObject = P Void
initiate = Pt absurd
instance MonoidalProfunctor Pointed where
one = Pt Just
Pt f ** Pt g = Pt (\(a, b) -> liftA2 id (f a, g b))
-- | The smash product of pointed sets.
-- Monoids relative to the smash product are absorption monoids.
instance Monoidal POINTED where
type Unit = P ()
type P a ** P b = P (a, b)
withOb2 r = r
leftUnitor = Pt (Just . snd)
leftUnitorInv = Pt (Just . ((),))
rightUnitor = Pt (Just . fst)
rightUnitorInv = Pt (Just . (,()))
associator = Pt (\((a, b), c) -> Just (a, (b, c)))
associatorInv = Pt (\(a, (b, c)) -> Just ((a, b), c))
instance SymMonoidal POINTED where
swap = Pt (Just . swap)
-- No 'Proarrow.Category.Monoidal.Closed.Closed' instance, though pointed sets are closed under the
-- smash product (<https://ncatlab.org/nlab/show/pointed+object#ClosedMonoidalStructure>): the
-- internal hom would need a type @x@ with @Maybe x ≅ (a -> Maybe b)@, the functions other than
-- @const Nothing@, and that is no Haskell type. @a -> Maybe b@ itself is too big by that one
-- function.
instance Powered Type POINTED where
type P a ^ n = P (n -> Maybe a)
withObPower r = r
power f = Pt (\a -> Just \n -> unPt (f n) a)
unpower (Pt f) n = Pt (f >=> ($ n))
instance Copowered Type POINTED where
type n *. P a = P (n, a)
withObCopower r = r
copower f = Pt \(n, a) -> unPt (f n) a
uncopower (Pt f) n = Pt \a -> f (n, a)
instance Monoid (P Void) where
mempty = Pt (const Nothing)
mappend = Pt (Just . fst)
-- | Lift Hask monoids.
memptyDefault :: (Monoid a) => Unit ~> P a
memptyDefault = Pt (Just . mempty)
mappendDefault :: (Monoid a) => P a ** P a ~> P a
mappendDefault = Pt (Just . mappend)
-- | Conjunction with False = Nothing, True = Just ()
instance Monoid (P ()) where
mempty = memptyDefault
mappend = mappendDefault
instance Monoid (P [a]) where
mempty = memptyDefault
mappend = mappendDefault
instance Comonoid (P x) where
counit = Pt (Just . counit)
comult = Pt (Just . comult)
instance CocommutativeComonoid (P x)
instance CopyDiscard POINTED
-- | Categories with a zero object can be seen as categories enriched in Pointed.
underlyingPt :: (HasZeroObject k) => (a :: k) ~> b -> Unit ~> P (a ~> b)
underlyingPt f = Pt \() -> Just f
enrichedPt :: (Ob (a :: k), Ob b, HasZeroObject k) => Unit ~> P (a ~> b) -> a ~> b
enrichedPt (Pt f) = P.fromMaybe zero (f ())
compPt :: (Ob (a :: k), Ob b, Ob c, HasZeroObject k) => P (b ~> c) ** P (a ~> b) ~> P (a ~> c)
compPt = Pt \(bc, ab) -> Just (bc . ab)
type FromPointed :: (Type -> Type) -> (POINTED -> Type)
data FromPointed f a where
FromPointed :: {unFromPointed :: f a} -> FromPointed f (P a)
type Filterable f = Functor (FromPointed f)
mapMaybe :: (Filterable f) => (a -> Maybe b) -> f a -> f b
mapMaybe f = unFromPointed . map (Pt f) . FromPointed
instance Functor (FromPointed []) where
map (Pt f) (FromPointed as) = FromPointed (P.mapMaybe f as)
instance Functor (FromPointed (Map.Map k)) where
map (Pt f) (FromPointed m) = FromPointed (Map.mapMaybe f m)
-- | Not quite Align from the semialign package.
-- This requires being able to dynamically decide per position if it is included in the result.
-- So more like @merge@ from Data.Map.
type Align f = Applicative (FromProd (FromPointed f))
alignWith :: (Align f) => (These a b -> Maybe c) -> f a -> f b -> f c
alignWith f fa fb = unFromPointed $ unFromProd $ liftA2 (Prod (Pt f)) (FromProd (FromPointed fa), FromProd (FromPointed fb))
nil :: (Align f) => f a
nil = unFromPointed $ unFromProd $ pure (Prod (Pt (const Nothing))) ()
instance Applicative (FromProd (FromPointed [])) where
pure a () = FromProd (FromPointed []) \\ a
liftA2 (Prod (Pt f)) (FromProd (FromPointed fa), FromProd (FromPointed fb)) = FromProd (FromPointed (merge fa fb))
where
merge as [] = mapMaybe (f . This) as
merge [] bs = mapMaybe (f . That) bs
merge (a : as) (b : bs) = case f (These a b) of
Nothing -> merge as bs
Just c -> c : merge as bs
instance (Ord k) => Applicative (FromProd (FromPointed (Map.Map k))) where
pure a () = FromProd (FromPointed Map.empty) \\ a
liftA2 (Prod (Pt f)) (FromProd (FromPointed fa), FromProd (FromPointed fb)) = FromProd (FromPointed (merge fa fb))
where
merge =
Map.merge
(Map.mapMaybeMissing \_ a -> f (This a))
(Map.mapMaybeMissing \_ b -> f (That b))
(Map.zipWithMaybeMatched \_ a b -> f (These a b))