packages feed

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

-- | The path category of a site: objects, morphisms-as-paths, and enumeration of the
-- morphisms between two objects.
module Moonlight.Category.Pure.Site.Category
  ( SitePathCategory,
    SitePathObject,
    SitePathMorphism,
    sitePathCategory,
    sitePathCategoryKernel,
    sitePathCategoryCodomain,
    sitePathCategoryObjectIds,
    sitePathManifest,
    sitePathObjectCategory,
    sitePathObjectValue,
    sitePathObjectCodomain,
    sitePathMorphismCategory,
    sitePathMorphismNodes,
    sitePathMorphismCodomain,
    mkSitePathObject,
    mkSitePathMorphism,
    sitePathMorphismsBetween,
  )
where

import Data.Kind (Type)
import Data.Bifunctor (first)
import Data.Function ((&))
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (mapMaybe)
import qualified Data.Set as Set
import Data.Tree (foldTree, unfoldTree)
import Moonlight.Category.Pure.Category (Category (..))
import Moonlight.Category.Pure.FinCat
  ( FinCat,
    FinCatError,
    FinMor,
    FinObjectId,
    FinObj,
  )
import Moonlight.Core qualified as Aggregate
import Moonlight.Category.Pure.Site.Compile
  ( ThinSiteKernel,
    thinSiteFinMorphism,
    thinSiteFinObject,
    thinSiteKernelCodomain,
    thinSiteKernelManifest,
    thinSiteKernelObjectIds,
  )
import Moonlight.Category.Pure.Site.Core (SiteManifest (..))
import Moonlight.Category.Pure.Site.Graph (siteImportEdges)

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

type SitePathObject :: Type -> Type
data SitePathObject obj = SitePathObject
  { sitePathObjectCategory :: SitePathCategory obj,
    sitePathObjectValue :: obj,
    sitePathObjectCodomain :: FinObj
  }
  deriving stock (Eq, Show)

type SitePathMorphism :: Type -> Type
data SitePathMorphism obj = SitePathMorphism
  { sitePathMorphismCategory :: SitePathCategory obj,
    sitePathMorphismNodes :: NonEmpty obj,
    sitePathMorphismCodomain :: FinMor
  }
  deriving stock (Eq, Show)

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

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

type SitePathCategoryError :: Type -> Type
data SitePathCategoryError obj
  = SitePathObjectWrongCategory
  | SitePathMorphismWrongCategory
  | SitePathCodomainError FinCatError
  deriving stock (Eq, Show)

sitePathCategory :: ThinSiteKernel obj -> SitePathCategory obj
sitePathCategory = SitePathCategory

sitePathCategoryCodomain :: SitePathCategory obj -> FinCat
sitePathCategoryCodomain = thinSiteKernelCodomain . sitePathCategoryKernel

sitePathCategoryObjectIds :: SitePathCategory obj -> Map obj FinObjectId
sitePathCategoryObjectIds = thinSiteKernelObjectIds . sitePathCategoryKernel

sitePathManifest :: SitePathCategory obj -> SiteManifest obj
sitePathManifest = thinSiteKernelManifest . sitePathCategoryKernel

mkSitePathObject :: Ord obj => SitePathCategory obj -> obj -> Maybe (SitePathObject obj)
mkSitePathObject category objectValue =
  case thinSiteFinObject (sitePathCategoryKernel category) objectValue of
    Left _ ->
      Nothing
    Right codomainObject ->
      Just
        SitePathObject
          { sitePathObjectCategory = category,
            sitePathObjectValue = objectValue,
            sitePathObjectCodomain = codomainObject
          }

mkSitePathMorphism :: Ord obj => SitePathCategory obj -> NonEmpty obj -> Maybe (SitePathMorphism obj)
mkSitePathMorphism category nodes =
  if validPath category nodes
    then sitePathMorphismFromValidatedNodes category nodes
    else Nothing

sitePathMorphismFromValidatedNodes :: Ord obj => SitePathCategory obj -> NonEmpty obj -> Maybe (SitePathMorphism obj)
sitePathMorphismFromValidatedNodes category nodes = do
  codomainMorphism <- either (const Nothing) Just (thinSiteFinMorphism (sitePathCategoryKernel category) nodes)
  pure
    SitePathMorphism
      { sitePathMorphismCategory = category,
        sitePathMorphismNodes = nodes,
        sitePathMorphismCodomain = codomainMorphism
      }

sitePathMorphismsBetween ::
  Ord obj =>
  SitePathCategory obj ->
  obj ->
  obj ->
  [SitePathMorphism obj]
sitePathMorphismsBetween category sourceValue targetValue =
  allPathNodes category sourceValue targetValue
    & mapMaybe (sitePathMorphismFromValidatedNodes category)

instance Ord obj => Category (SitePathCategory obj) where
  type Ob (SitePathCategory obj) = SitePathObject obj
  type Mor (SitePathCategory obj) = SitePathMorphism obj
  type TwoMor (SitePathCategory obj) = SitePathTwoMor obj
  type Compositor (SitePathCategory obj) = SitePathCompositor obj
  type CategoryError (SitePathCategory obj) = SitePathCategoryError obj

  identity category objectValue =
    if sitePathObjectCategory objectValue /= category
      then Left SitePathObjectWrongCategory
      else do
        codomainMorphism <-
          first SitePathCodomainError
            (identity (sitePathCategoryCodomain category) (sitePathObjectCodomain objectValue))
        Right
          SitePathMorphism
            { sitePathMorphismCategory = category,
              sitePathMorphismNodes = sitePathObjectValue objectValue :| [],
              sitePathMorphismCodomain = codomainMorphism
            }

  compose category left right
    | sitePathMorphismCategory left /= category = Left SitePathMorphismWrongCategory
    | sitePathMorphismCategory right /= category = Left SitePathMorphismWrongCategory
    | otherwise = do
        (codomainMorphism, _) <-
          first SitePathCodomainError
            (compose (sitePathCategoryCodomain category) (sitePathMorphismCodomain left) (sitePathMorphismCodomain right))
        let leftNodes = sitePathMorphismNodes left
            rightNodes = sitePathMorphismNodes right
            mergedPath =
              NonEmpty.head rightNodes
                :| (NonEmpty.tail rightNodes <> drop 1 (NonEmpty.toList leftNodes))
        Right
          ( SitePathMorphism
              { sitePathMorphismCategory = category,
                sitePathMorphismNodes = mergedPath,
                sitePathMorphismCodomain = codomainMorphism
              },
            SitePathCompositor
          )

  source category morphism =
    if sitePathMorphismCategory morphism /= category
      then Left SitePathMorphismWrongCategory
      else do
        codomainObject <-
          first SitePathCodomainError
            (source (sitePathCategoryCodomain category) (sitePathMorphismCodomain morphism))
        Right
          SitePathObject
            { sitePathObjectCategory = category,
              sitePathObjectValue = NonEmpty.head (sitePathMorphismNodes morphism),
              sitePathObjectCodomain = codomainObject
            }

  target category morphism =
    if sitePathMorphismCategory morphism /= category
      then Left SitePathMorphismWrongCategory
      else do
        codomainObject <-
          first SitePathCodomainError
            (target (sitePathCategoryCodomain category) (sitePathMorphismCodomain morphism))
        Right
          SitePathObject
            { sitePathObjectCategory = category,
              sitePathObjectValue = NonEmpty.last (sitePathMorphismNodes morphism),
              sitePathObjectCodomain = codomainObject
            }

validPath :: Ord obj => SitePathCategory obj -> NonEmpty obj -> Bool
validPath category nodes =
  let manifest = sitePathManifest category
      objects = siteObjects manifest
      allNodesPresent =
        nodes
          & NonEmpty.toList
          & all (`Set.member` objects)
      importEdges = siteImportEdges manifest
      consecutive =
        Aggregate.adjacentPairs (NonEmpty.toList nodes)
          & all (`Set.member` importEdges)
   in allNodesPresent && consecutive

allPathNodes :: Ord obj => SitePathCategory obj -> obj -> obj -> [NonEmpty obj]
allPathNodes category sourceValue targetValue =
  let manifest = sitePathManifest category
      imports = siteImports manifest
      unfoldPath (current, visited) =
        ( current,
          if current == targetValue
            then []
            else
              Map.findWithDefault Set.empty current imports
                & Set.toAscList
                & filter (`Set.notMember` visited)
                & fmap (\next -> (next, Set.insert next visited))
        )
      collectPaths current childPaths
        | current == targetValue = [current :| []]
        | otherwise = foldMap (fmap (NonEmpty.cons current)) childPaths
   in foldTree collectPaths (unfoldTree unfoldPath (sourceValue, Set.singleton sourceValue))