packages feed

moonlight-delta-0.1.0.2: src-core/Moonlight/Delta/Scope.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeFamilies #-}

{-|
Dirty-scope tracking with one type-indexed notion of emptiness. A @Scope@
distinguishes no invalidation, a finite dirty set, and global invalidation.

> import Data.Set (Set)
> import Data.Set qualified as Set
> import Moonlight.Delta.Scope qualified as Scope
>
> dirtyPages :: Scope.Scope (Set Int)
> dirtyPages = Scope.dirtyScope (Set.fromList [2, 7])
>
> visibleDirtyPages :: Scope.Scope (Set Int)
> visibleDirtyPages = Scope.restrictScope (Set.singleton 7) dirtyPages

@scopeKeys@ returns @Nothing@ for @fullScope@.
-}
module Moonlight.Delta.Scope
  ( ScopeCarrier (..),
    Scope,
    Scoped (..),
    cleanScope,
    fullScope,
    dirtyScope,
    foldScope,
    cleanDelta,
    fullDelta,
    payloadDelta,
    scopedDelta,
    normalizeScope,
    normalizeScoped,
    scopeNull,
    scopedDeltaNull,
    scopedDeltaHasPayload,
    scopedDeltaSupport,
    scopedDeltaPayload,
    scopeKeys,
    unionScope,
    restrictScope,
    mapScope,
    mapScopedScope,
  )
where

import Data.Kind (Type)
import Data.Maybe (Maybe (..), isJust)
import Moonlight.Delta.Normalize
  ( DeltaNormalize (..),
  )
import Moonlight.Delta.Support
  ( DeltaSupport (..),
  )
import Moonlight.Core
  ( OrdSet (..),
  )
import Prelude
  ( Bool (..),
    Eq (..),
    Monoid (..),
    Ord (..),
    Semigroup (..),
    Show,
    id,
    not,
    otherwise,
    (&&),
    (.),
  )

type Scope :: Type -> Type
data Scope scope
  = CleanScope
  | DirtyScope !scope
  | FullScope
  deriving stock (Eq, Ord, Show)

type Scoped :: Type -> Type -> Type
data Scoped scope payload = Scoped
  { scope :: !(Scope scope),
    payload :: !(Maybe payload)
  }
  deriving stock (Eq, Ord, Show)

class ScopeCarrier scope where
  scopeCarrierNull :: scope -> Bool

instance {-# OVERLAPPABLE #-} OrdSet scope => ScopeCarrier scope where
  scopeCarrierNull =
    nullSet

instance (ScopeCarrier scope, Semigroup scope) => Semigroup (Scope scope) where
  (<>) =
    unionScope
  {-# INLINE (<>) #-}

instance (ScopeCarrier scope, Semigroup scope) => Monoid (Scope scope) where
  mempty =
    CleanScope
  {-# INLINE mempty #-}

instance (ScopeCarrier scope, Semigroup scope, Semigroup payload) => Semigroup (Scoped scope payload) where
  leftDelta <> rightDelta =
    normalizeScoped
      Scoped
        { scope =
            unionScope
              (scope leftDelta)
              (scope rightDelta),
          payload =
            payload leftDelta <> payload rightDelta
        }
  {-# INLINE (<>) #-}

instance (ScopeCarrier scope, Semigroup scope, Semigroup payload) => Monoid (Scoped scope payload) where
  mempty =
    cleanDelta
  {-# INLINE mempty #-}

instance DeltaNormalize (Scope scope) where
  normalizeDelta =
    normalizeScope
  {-# INLINE normalizeDelta #-}

  deltaNull =
    scopeNull
  {-# INLINE deltaNull #-}

instance DeltaSupport (Scope scope) where
  type DeltaSupportSet (Scope scope) = Scope scope

  emptySupport =
    CleanScope
  {-# INLINE emptySupport #-}

  deltaSupport =
    normalizeScope
  {-# INLINE deltaSupport #-}

instance DeltaNormalize (Scoped scope payload) where
  normalizeDelta =
    normalizeScoped
  {-# INLINE normalizeDelta #-}

  deltaNull =
    scopedDeltaNull
  {-# INLINE deltaNull #-}

instance DeltaSupport (Scoped scope payload) where
  type DeltaSupportSet (Scoped scope payload) = Scope scope

  emptySupport =
    CleanScope
  {-# INLINE emptySupport #-}

  deltaSupport =
    scopedDeltaSupport
  {-# INLINE deltaSupport #-}

cleanScope :: Scope scope
cleanScope =
  CleanScope
{-# INLINE cleanScope #-}

fullScope :: Scope scope
fullScope =
  FullScope
{-# INLINE fullScope #-}

dirtyScope ::
  ScopeCarrier scope =>
  scope ->
  Scope scope
dirtyScope keys
  | scopeCarrierNull keys =
      CleanScope
  | otherwise =
      DirtyScope keys
{-# INLINE dirtyScope #-}

cleanDelta :: Scoped scope payload
cleanDelta =
  Scoped
    { scope = cleanScope,
      payload = Nothing
    }
{-# INLINE cleanDelta #-}

fullDelta :: Scoped scope payload
fullDelta =
  Scoped
    { scope = fullScope,
      payload = Nothing
    }
{-# INLINE fullDelta #-}

payloadDelta :: payload -> Scoped scope payload
payloadDelta payload =
  Scoped
    { scope = cleanScope,
      payload = Just payload
    }
{-# INLINE payloadDelta #-}

scopedDelta ::
  Scope scope ->
  Maybe payload ->
  Scoped scope payload
scopedDelta scopeValue payloadValue =
  normalizeScoped
    Scoped
      { scope = scopeValue,
        payload = payloadValue
      }
{-# INLINE scopedDelta #-}

normalizeScope ::
  Scope scope ->
  Scope scope
normalizeScope =
  id
{-# INLINE normalizeScope #-}

foldScope ::
  result ->
  (scope -> result) ->
  result ->
  Scope scope ->
  result
foldScope cleanCase dirtyCase fullCase scopeValue =
  case scopeValue of
    CleanScope ->
      cleanCase
    DirtyScope keys ->
      dirtyCase keys
    FullScope ->
      fullCase
{-# INLINE foldScope #-}

normalizeScoped ::
  Scoped scope payload ->
  Scoped scope payload
normalizeScoped =
  id
{-# INLINE normalizeScoped #-}

scopeNull ::
  Scope scope ->
  Bool
scopeNull scopeValue =
  case scopeValue of
    CleanScope ->
      True
    DirtyScope _ ->
      False
    FullScope ->
      False
{-# INLINE scopeNull #-}

scopedDeltaHasPayload :: Scoped scope payload -> Bool
scopedDeltaHasPayload =
  isJust . payload
{-# INLINE scopedDeltaHasPayload #-}

scopedDeltaNull ::
  Scoped scope payload ->
  Bool
scopedDeltaNull delta =
  scopeNull (scope delta)
    && not (scopedDeltaHasPayload delta)
{-# INLINE scopedDeltaNull #-}

scopedDeltaSupport ::
  Scoped scope payload ->
  Scope scope
scopedDeltaSupport =
  scope
{-# INLINE scopedDeltaSupport #-}

scopedDeltaPayload :: Scoped scope payload -> Maybe payload
scopedDeltaPayload =
  payload
{-# INLINE scopedDeltaPayload #-}

scopeKeys ::
  OrdSet scope =>
  Scope scope ->
  Maybe scope
scopeKeys scopeValue =
  case scopeValue of
    CleanScope ->
      Just emptySet
    DirtyScope keys ->
      Just keys
    FullScope ->
      Nothing
{-# INLINE scopeKeys #-}

unionScope ::
  (ScopeCarrier scope, Semigroup scope) =>
  Scope scope ->
  Scope scope ->
  Scope scope
unionScope leftScope rightScope =
  case (leftScope, rightScope) of
    (FullScope, _) ->
      FullScope
    (_, FullScope) ->
      FullScope
    (CleanScope, scopeValue) ->
      scopeValue
    (scopeValue, CleanScope) ->
      scopeValue
    (DirtyScope leftKeys, DirtyScope rightKeys) ->
      dirtyScope (leftKeys <> rightKeys)
{-# INLINE unionScope #-}

restrictScope ::
  OrdSet scope =>
  scope ->
  Scope scope ->
  Scope scope
restrictScope restriction scopeValue =
  if nullSet restriction
    then cleanScope
    else
      case scopeValue of
        CleanScope ->
          cleanScope
        DirtyScope keys ->
          dirtyScope (intersectionSet restriction keys)
        FullScope ->
          dirtyScope restriction
{-# INLINE restrictScope #-}

mapScope ::
  ScopeCarrier targetScope =>
  (sourceScope -> targetScope) ->
  Scope sourceScope ->
  Scope targetScope
mapScope project scopeValue =
  case scopeValue of
    CleanScope ->
      cleanScope
    DirtyScope keys ->
      dirtyScope (project keys)
    FullScope ->
      fullScope
{-# INLINE mapScope #-}

mapScopedScope ::
  ScopeCarrier targetScope =>
  (sourceScope -> targetScope) ->
  Scoped sourceScope payload ->
  Scoped targetScope payload
mapScopedScope project delta =
  Scoped
    { scope = mapScope project (scope delta),
      payload = payload delta
    }
{-# INLINE mapScopedScope #-}