packages feed

moonlight-category-1.1.0.0: src-site/Moonlight/Category/Pure/Site/Quotient.hs

{-# LANGUAGE DataKinds #-}

-- | The path-thin quotient of a fully validated site path category: quotient
-- objects and morphisms, and the quotient maps from the path category.
module Moonlight.Category.Pure.Site.Quotient
  ( PathThinCat (..),
    PathThinObject (..),
    PathThinMorphism (..),
    SitePathQuotient,
    sitePathQuotientDomain,
    sitePathQuotientCodomain,
    SitePathQuotientError (..),
    pathThinCat,
    mkPathThinObject,
    mkPathThinMorphism,
    quotientPathThinObject,
    quotientPathThinMorphism,
    pathThinCodomainObject,
    pathThinCodomainMorphism,
    sitePathQuotient,
    quotientMapObject,
    quotientMapMorphism,
  )
where

import Data.Kind (Type)
import Data.Bifunctor (first)
import qualified Data.List.NonEmpty as NonEmpty
import Moonlight.Category.Pure.Category (Category (..))
import Moonlight.Category.Pure.FinCat
  ( FinCat,
    FinMor,
    FinMorphismId,
    FinObjectId,
    FinObj,
  )
import Moonlight.Category.Pure.Site.Category
  ( SitePathCategory,
    SitePathMorphism,
    SitePathObject,
    mkSitePathObject,
    sitePathCategoryCodomain,
    sitePathCategoryKernel,
    sitePathManifest,
    sitePathMorphismCategory,
    sitePathMorphismCodomain,
    sitePathMorphismNodes,
    sitePathObjectCategory,
    sitePathObjectCodomain,
    sitePathObjectValue,
  )
import Moonlight.Category.Pure.Site.Compile
  ( ThinSiteKernel,
    ThinSiteValidation (SiteValidated),
    ThinSiteLookupError (..),
    thinSiteFinMorphismByEndpoints,
    thinSiteFinObject,
  )
import Moonlight.Category.Pure.Site.Core (SiteManifest (..))

type PathThinCat :: Type -> Type
newtype PathThinCat obj = PathThinCat
  { pathThinDomain :: SitePathCategory obj
  }
  deriving stock (Eq, Show)

type PathThinObject :: Type -> Type
data PathThinObject obj = PathThinObject
  { pathThinObjectCategory :: PathThinCat obj,
    pathThinObjectValue :: obj,
    pathThinObjectCodomain :: FinObj
  }
  deriving stock (Eq, Show)

type PathThinMorphism :: Type -> Type
data PathThinMorphism obj = PathThinMorphism
  { pathThinMorphismCategory :: PathThinCat obj,
    pathThinMorphismSourceValue :: obj,
    pathThinMorphismTargetValue :: obj,
    pathThinMorphismWitness :: SitePathMorphism obj,
    pathThinMorphismCodomain :: FinMor
  }
  deriving stock (Show)

instance Eq obj => Eq (PathThinMorphism obj) where
  left == right =
    pathThinMorphismCategory left == pathThinMorphismCategory right
      && pathThinMorphismSourceValue left == pathThinMorphismSourceValue right
      && pathThinMorphismTargetValue left == pathThinMorphismTargetValue right

type PathThinCompositor :: Type -> Type
data PathThinCompositor obj
  = PathThinCompositor
  deriving stock (Eq, Show)

type PathThinTwoMor :: Type -> Type
data PathThinTwoMor obj
  = PathThinTwoMor
  deriving stock (Eq, Show)

type PathThinCategoryError :: Type -> Type
data PathThinCategoryError obj
  = PathThinObjectWrongCategory
  | PathThinMorphismWrongCategory
  | PathThinMorphismNotComposable
  | PathThinInvalidIdentity
  | PathThinInvalidComposite
  | PathThinInvalidSourceTarget
  deriving stock (Eq, Show)

type SitePathQuotient :: Type -> Type
newtype SitePathQuotient obj = SitePathQuotient
  { sitePathQuotientDomain :: SitePathCategory obj
  }

sitePathQuotientCodomain :: SitePathQuotient obj -> FinCat
sitePathQuotientCodomain = sitePathCategoryCodomain . sitePathQuotientDomain

type SitePathQuotientError :: Type -> Type
data SitePathQuotientError obj
  = QuotientUnknownObject obj
  | QuotientCodomainObjectMissing FinObjectId
  | QuotientUnknownMorphismPair obj obj
  | QuotientCodomainMorphismMissing FinMorphismId
  | QuotientCodomainMorphismInvalid
  | QuotientObjectWrongDomain
  | QuotientMorphismWrongDomain
  deriving stock (Eq, Show)

pathThinCat :: SitePathCategory obj -> PathThinCat obj
pathThinCat = PathThinCat

mkPathThinObject :: Ord obj => PathThinCat obj -> obj -> Maybe (PathThinObject obj)
mkPathThinObject category objectValue = do
  siteObject <- mkSitePathObject (pathThinDomain category) objectValue
  pure
    PathThinObject
      { pathThinObjectCategory = category,
        pathThinObjectValue = objectValue,
        pathThinObjectCodomain = sitePathObjectCodomain siteObject
      }

mkPathThinMorphism ::
  Ord obj =>
  PathThinCat obj ->
  SitePathMorphism obj ->
  Maybe (PathThinMorphism obj)
mkPathThinMorphism category witness =
  if sitePathMorphismCategory witness == pathThinDomain category
    then
      let sourceValue = NonEmpty.head (sitePathMorphismNodes witness)
          targetValue = NonEmpty.last (sitePathMorphismNodes witness)
       in Just
            PathThinMorphism
              { pathThinMorphismCategory = category,
                pathThinMorphismSourceValue = sourceValue,
                pathThinMorphismTargetValue = targetValue,
                pathThinMorphismWitness = witness,
                pathThinMorphismCodomain = sitePathMorphismCodomain witness
              }
    else Nothing

quotientPathThinObject :: SitePathObject obj -> PathThinObject obj
quotientPathThinObject objectValue =
  PathThinObject
    { pathThinObjectCategory = pathThinCat (sitePathObjectCategory objectValue),
      pathThinObjectValue = sitePathObjectValue objectValue,
      pathThinObjectCodomain = sitePathObjectCodomain objectValue
    }

quotientPathThinMorphism :: SitePathMorphism obj -> PathThinMorphism obj
quotientPathThinMorphism morphism =
  PathThinMorphism
    { pathThinMorphismCategory = pathThinCat (sitePathMorphismCategory morphism),
      pathThinMorphismSourceValue = NonEmpty.head (sitePathMorphismNodes morphism),
      pathThinMorphismTargetValue = NonEmpty.last (sitePathMorphismNodes morphism),
      pathThinMorphismWitness = morphism,
      pathThinMorphismCodomain = sitePathMorphismCodomain morphism
    }

pathThinCodomainObject :: PathThinObject obj -> FinObj
pathThinCodomainObject =
  pathThinObjectCodomain

pathThinCodomainMorphism :: PathThinMorphism obj -> FinMor
pathThinCodomainMorphism =
  pathThinMorphismCodomain

sitePathQuotient :: SitePathCategory obj -> SitePathQuotient obj
sitePathQuotient = SitePathQuotient

sameSitePathDomain :: Ord obj => SitePathCategory obj -> SitePathQuotient obj -> Bool
sameSitePathDomain category quotient =
  let categoryManifest = sitePathManifest category
      quotientManifest = sitePathManifest (sitePathQuotientDomain quotient)
   in siteObjects categoryManifest == siteObjects quotientManifest
        && siteImports categoryManifest == siteImports quotientManifest

quotientMapObject ::
  Ord obj =>
  SitePathQuotient obj ->
  SitePathObject obj ->
  Either (SitePathQuotientError obj) FinObj
quotientMapObject quotient objectValue
  | not (sameSitePathDomain (sitePathObjectCategory objectValue) quotient) =
      Left QuotientObjectWrongDomain
  | otherwise =
      first fromThinSiteLookupError
        ( thinSiteFinObject
            (sitePathQuotientKernel quotient)
            (sitePathObjectValue objectValue)
        )

quotientMapMorphism ::
  Ord obj =>
  SitePathQuotient obj ->
  SitePathMorphism obj ->
  Either (SitePathQuotientError obj) FinMor
quotientMapMorphism quotient morphism
  | not (sameSitePathDomain (sitePathMorphismCategory morphism) quotient) =
      Left QuotientMorphismWrongDomain
  | otherwise =
      first fromThinSiteLookupError
        ( thinSiteFinMorphismByEndpoints
            (sitePathQuotientKernel quotient)
            (NonEmpty.head (sitePathMorphismNodes morphism))
            (NonEmpty.last (sitePathMorphismNodes morphism))
        )

instance Ord obj => Category (PathThinCat obj) where
  type Ob (PathThinCat obj) = PathThinObject obj
  type Mor (PathThinCat obj) = PathThinMorphism obj
  type TwoMor (PathThinCat obj) = PathThinTwoMor obj
  type Compositor (PathThinCat obj) = PathThinCompositor obj
  type CategoryError (PathThinCat obj) = PathThinCategoryError obj

  identity category objectValue =
    if pathThinObjectCategory objectValue /= category
      then Left PathThinObjectWrongCategory
      else
        case mkSitePathObject (pathThinDomain category) (pathThinObjectValue objectValue) of
          Nothing -> Left PathThinInvalidIdentity
          Just siteObject -> do
            witness <- first (const PathThinInvalidIdentity) (identity (pathThinDomain category) siteObject)
            codomain <- first (const PathThinInvalidIdentity) (identity (sitePathCategoryCodomain (pathThinDomain category)) (pathThinObjectCodomain objectValue))
            Right
              PathThinMorphism
                { pathThinMorphismCategory = category,
                  pathThinMorphismSourceValue = pathThinObjectValue objectValue,
                  pathThinMorphismTargetValue = pathThinObjectValue objectValue,
                  pathThinMorphismWitness = witness,
                  pathThinMorphismCodomain = codomain
                }

  compose category left right
    | pathThinMorphismCategory left /= category = Left PathThinMorphismWrongCategory
    | pathThinMorphismCategory right /= category = Left PathThinMorphismWrongCategory
    | pathThinMorphismTargetValue right /= pathThinMorphismSourceValue left = Left PathThinMorphismNotComposable
    | otherwise = do
        (witnessComposed, _) <-
          first
            (const PathThinInvalidComposite)
            (compose (pathThinDomain category) (pathThinMorphismWitness left) (pathThinMorphismWitness right))
        case mkPathThinMorphism category witnessComposed of
          Nothing -> Left PathThinInvalidComposite
          Just morphism -> Right (morphism, PathThinCompositor)

  source category morphism =
    if pathThinMorphismCategory morphism /= category
      then Left PathThinMorphismWrongCategory
      else do
        codomainObject <-
          first
            (const PathThinInvalidSourceTarget)
            (source (sitePathCategoryCodomain (pathThinDomain category)) (pathThinMorphismCodomain morphism))
        Right
          PathThinObject
            { pathThinObjectCategory = category,
              pathThinObjectValue = pathThinMorphismSourceValue morphism,
              pathThinObjectCodomain = codomainObject
            }

  target category morphism =
    if pathThinMorphismCategory morphism /= category
      then Left PathThinMorphismWrongCategory
      else do
        codomainObject <-
          first
            (const PathThinInvalidSourceTarget)
            (target (sitePathCategoryCodomain (pathThinDomain category)) (pathThinMorphismCodomain morphism))
        Right
          PathThinObject
            { pathThinObjectCategory = category,
              pathThinObjectValue = pathThinMorphismTargetValue morphism,
              pathThinObjectCodomain = codomainObject
            }

sitePathQuotientKernel :: SitePathQuotient obj -> ThinSiteKernel 'SiteValidated obj
sitePathQuotientKernel = sitePathCategoryKernel . sitePathQuotientDomain

fromThinSiteLookupError :: ThinSiteLookupError obj -> SitePathQuotientError obj
fromThinSiteLookupError lookupError =
  case lookupError of
    ThinSiteUnknownObject objectValue ->
      QuotientUnknownObject objectValue
    ThinSiteCodomainObjectMissing objectId ->
      QuotientCodomainObjectMissing objectId
    ThinSiteUnknownMorphismPair sourceValue targetValue ->
      QuotientUnknownMorphismPair sourceValue targetValue
    ThinSiteCodomainMorphismMissing morId ->
      QuotientCodomainMorphismMissing morId
    ThinSiteCodomainMorphismInvalid _ ->
      QuotientCodomainMorphismInvalid