packages feed

moonlight-category-0.1.0.0: src-finite/Moonlight/Category/Pure/FinCat/Functor.hs

-- | Object maps between finite thin categories, validated once against the
-- source and target reachability relations.
module Moonlight.Category.Pure.FinCat.Functor
  ( FinThinFunctor,
    FinThinFunctorValidationError (..),
    FinThinFunctorApplicationError (..),
    mkFinThinFunctor,
    finThinFunctorSource,
    finThinFunctorTarget,
    finThinFunctorObjectMap,
    applyFinThinFunctor,
  )
where

import Data.Kind (Type)
import Data.List (find)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (listToMaybe)
import qualified Data.Set as Set
import Moonlight.Category.Pure.FinCat
  ( FinCat,
    FinObjectId,
    finCatExplicitMorphismMapView,
    finCatIsThin,
    finCatObjects,
  )

-- | A validated functor between finite thin categories. Thinness makes the
-- morphism action proof-irrelevant: a total object action is functorial exactly
-- when it preserves every source reachability relation.
type FinThinFunctor :: Type
data FinThinFunctor = FinThinFunctor
  { finThinFunctorSource :: !FinCat,
    finThinFunctorTarget :: !FinCat,
    finThinFunctorObjectMap :: !(Map FinObjectId FinObjectId)
  }

type FinThinFunctorValidationError :: Type
data FinThinFunctorValidationError
  = FinThinFunctorSourceNotThin
  | FinThinFunctorTargetNotThin
  | FinThinFunctorMissingSourceObject !FinObjectId
  | FinThinFunctorUnexpectedSourceObject !FinObjectId
  | FinThinFunctorTargetObjectAbsent !FinObjectId !FinObjectId
  | FinThinFunctorOrderNotPreserved !FinObjectId !FinObjectId !FinObjectId !FinObjectId
  deriving stock (Eq, Ord, Show)

type FinThinFunctorApplicationError :: Type
data FinThinFunctorApplicationError
  = FinThinFunctorUnknownSourceObject !FinObjectId
  deriving stock (Eq, Ord, Show)

-- | Check a total finite object table once and retain its thin-functor proof.
mkFinThinFunctor ::
  FinCat ->
  FinCat ->
  Map FinObjectId FinObjectId ->
  Either FinThinFunctorValidationError FinThinFunctor
mkFinThinFunctor sourceCategory targetCategory objectMap = do
  requireThin FinThinFunctorSourceNotThin sourceCategory
  requireThin FinThinFunctorTargetNotThin targetCategory
  validateObjectMapDomain
  validateObjectMapCodomain
  validateOrderPreservation
  Right
    FinThinFunctor
      { finThinFunctorSource = sourceCategory,
        finThinFunctorTarget = targetCategory,
        finThinFunctorObjectMap = objectMap
      }
  where
    sourceObjects = finCatObjects sourceCategory
    targetObjects = finCatObjects targetCategory
    suppliedSourceObjects = Map.keysSet objectMap

    validateObjectMapDomain =
      case Set.lookupMin (Set.difference sourceObjects suppliedSourceObjects) of
        Just missingObject -> Left (FinThinFunctorMissingSourceObject missingObject)
        Nothing ->
          case Set.lookupMin (Set.difference suppliedSourceObjects sourceObjects) of
            Just unexpectedObject -> Left (FinThinFunctorUnexpectedSourceObject unexpectedObject)
            Nothing -> Right ()

    validateObjectMapCodomain =
      case find (not . (`Set.member` targetObjects) . snd) (Map.toAscList objectMap) of
        Just (sourceObject, targetObject) ->
          Left (FinThinFunctorTargetObjectAbsent sourceObject targetObject)
        Nothing -> Right ()

    validateOrderPreservation =
      case findOrderViolation of
        Just (sourceObject, sourceTarget, mappedSource, mappedTarget) ->
          Left
            ( FinThinFunctorOrderNotPreserved
                sourceObject
                sourceTarget
                mappedSource
                mappedTarget
            )
        Nothing -> Right ()

    findOrderViolation =
      listToMaybe
        [ (sourceObject, targetObject, mappedSource, mappedTarget)
        | ((sourceObject, targetObject), morphisms) <- Map.toAscList sourceMorphismMap,
          not (null morphisms),
          Just mappedSource <- [Map.lookup sourceObject objectMap],
          Just mappedTarget <- [Map.lookup targetObject objectMap],
          not (targetRelates mappedSource mappedTarget)
        ]

    sourceMorphismMap = finCatExplicitMorphismMapView sourceCategory
    targetMorphismMap = finCatExplicitMorphismMapView targetCategory

    targetRelates sourceObject targetObject =
      sourceObject == targetObject
        || not
          ( null
              ( Map.findWithDefault
                  []
                  (sourceObject, targetObject)
                  targetMorphismMap
              )
          )

requireThin :: FinThinFunctorValidationError -> FinCat -> Either FinThinFunctorValidationError ()
requireThin notThinError categoryValue
  | finCatIsThin categoryValue = Right ()
  | otherwise = Left notThinError

applyFinThinFunctor ::
  FinThinFunctor ->
  FinObjectId ->
  Either FinThinFunctorApplicationError FinObjectId
applyFinThinFunctor functorValue sourceObject =
  case Map.lookup sourceObject (finThinFunctorObjectMap functorValue) of
    Just targetObject -> Right targetObject
    Nothing -> Left (FinThinFunctorUnknownSourceObject sourceObject)