packages feed

moonlight-category-0.1.0.0: src-abstract/Moonlight/Category/Pure/StructuredCospan.hs

{-# LANGUAGE TypeFamilies #-}

-- | Structured cospans: two legs into a shared apex carrying a decoration, with
-- boundary projections and pushout composition.
module Moonlight.Category.Pure.StructuredCospan
  ( StructuredCospan,
    structuredLeftLeg,
    structuredRightLeg,
    structuredApex,
    structuredDecoration,
    mkStructuredCospan,
    leftBoundary,
    rightBoundary,
    composeStructuredCospan,
    StructuredCospanError (..),
  )
where

import Data.Bifunctor (first)
import Data.Kind (Type)
import Moonlight.Category.Pure.Category (Category (..), composeMor)
import Moonlight.Category.Pure.Limits (HasPushouts (..))

type StructuredCospan :: Type -> Type -> Type
-- | Two boundary morphisms with a common apex and an attached decoration.
data StructuredCospan category decoration = StructuredCospan
  { structuredLeftBoundary :: Ob category,
    structuredRightBoundary :: Ob category,
    -- | The morphism from the left boundary into the apex.
    structuredLeftLeg :: Mor category,
    -- | The morphism from the right boundary into the apex.
    structuredRightLeg :: Mor category,
    -- | The common codomain of both legs.
    structuredApex :: Ob category,
    -- | Data carried by the apex.
    structuredDecoration :: decoration
  }

type StructuredCospanError :: Type -> Type
-- | Failure to inspect, align, or push out structured boundaries.
data StructuredCospanError category
  = StructuredCospanCategoryError (CategoryError category)
  | StructuredCospanBoundaryMismatch (Ob category) (Ob category)
  | StructuredCospanPushoutMissing (Mor category) (Mor category)

-- | Validate that both legs share an apex.
mkStructuredCospan :: (Category category, Eq (Ob category)) => category -> Mor category -> Mor category -> decoration -> Either (StructuredCospanError category) (StructuredCospan category decoration)
mkStructuredCospan categoryValue leftLeg rightLeg decoration = do
  leftSource <- first StructuredCospanCategoryError (source categoryValue leftLeg)
  rightSource <- first StructuredCospanCategoryError (source categoryValue rightLeg)
  leftTarget <- first StructuredCospanCategoryError (target categoryValue leftLeg)
  rightTarget <- first StructuredCospanCategoryError (target categoryValue rightLeg)
  if leftTarget == rightTarget
    then Right (StructuredCospan leftSource rightSource leftLeg rightLeg leftTarget decoration)
    else Left (StructuredCospanBoundaryMismatch leftTarget rightTarget)
{-# INLINE mkStructuredCospan #-}

-- | Project the left boundary through the category's failure surface.
leftBoundary :: category -> StructuredCospan category decoration -> Either (CategoryError category) (Ob category)
leftBoundary _ =
  Right . structuredLeftBoundary
{-# INLINE leftBoundary #-}

-- | Project the right boundary through the category's failure surface.
rightBoundary :: category -> StructuredCospan category decoration -> Either (CategoryError category) (Ob category)
rightBoundary _ =
  Right . structuredRightBoundary
{-# INLINE rightBoundary #-}

-- | Glue matching boundaries by pushout and combine their decorations.
composeStructuredCospan ::
  (HasPushouts category, Eq (Ob category)) =>
  category ->
  (leftDecoration -> rightDecoration -> combinedDecoration) ->
  StructuredCospan category leftDecoration ->
  StructuredCospan category rightDecoration ->
  Either (StructuredCospanError category) (StructuredCospan category combinedDecoration)
composeStructuredCospan categoryValue combineDecorations leftCospan rightCospan = do
  if structuredRightBoundary leftCospan == structuredLeftBoundary rightCospan
    then Right ()
    else Left (StructuredCospanBoundaryMismatch (structuredRightBoundary leftCospan) (structuredLeftBoundary rightCospan))
  (pushoutObject, pushoutLeft, pushoutRight) <-
    maybe
      (Left (StructuredCospanPushoutMissing (structuredRightLeg leftCospan) (structuredLeftLeg rightCospan)))
      Right
      (pushout categoryValue (structuredRightLeg leftCospan) (structuredLeftLeg rightCospan))
  composedLeft <- first StructuredCospanCategoryError (composeMor categoryValue pushoutLeft (structuredLeftLeg leftCospan))
  composedRight <- first StructuredCospanCategoryError (composeMor categoryValue pushoutRight (structuredRightLeg rightCospan))
  pure
    ( StructuredCospan
        (structuredLeftBoundary leftCospan)
        (structuredRightBoundary rightCospan)
        composedLeft
        composedRight
        pushoutObject
        (combineDecorations (structuredDecoration leftCospan) (structuredDecoration rightCospan))
    )
{-# INLINE composeStructuredCospan #-}