packages feed

moonlight-delta-0.1.0.0: src-core/Moonlight/Delta/Monotone.hs

{-# LANGUAGE DerivingStrategies #-}

-- | Monotone deltas are relative increments plus an explicit rebase form.
--
-- The application and composition laws hold exactly when the supplied join
-- function is associative. Join-over-Reset composes to
-- @ResetDelta (join target increment)@, which agrees with sequential
-- application on the nose; Join-over-Join is where associativity is required.
--
-- 'ResetDelta' is the deliberate non-monotone rebase primitive. Composition
-- treats a newer 'ResetDelta' as left-absorbing, so rebasing discards all older
-- increments by construction.
module Moonlight.Delta.Monotone
  ( Monotone (..),
    applyDelta,
    composeDelta,
    mapDelta,
  )
where

import Data.Kind (Type)
import Prelude (Eq, Ord, Show)

type Monotone :: Type -> Type
data Monotone value
  = JoinDelta !value
  | ResetDelta !value
  deriving stock (Eq, Ord, Show)

applyDelta ::
  (value -> value -> value) ->
  Monotone value ->
  value ->
  value
applyDelta joinValue delta current =
  case delta of
    JoinDelta increment ->
      joinValue current increment
    ResetDelta target ->
      target

-- | Compose a newer monotone delta over an older monotone delta.
--
-- Precondition: the supplied join function must be associative for
-- 'applyDelta' over 'composeDelta' to agree with sequential
-- application for every pair of deltas. A newer 'ResetDelta' is left-absorbing:
-- @composeDelta joinValue (ResetDelta target) older = ResetDelta target@.
-- An older 'ResetDelta' followed by @JoinDelta increment@ composes to
-- @ResetDelta (joinValue target increment)@.
composeDelta ::
  (value -> value -> value) ->
  Monotone value ->
  Monotone value ->
  Monotone value
composeDelta joinValue newer older =
  case newer of
    ResetDelta target ->
      ResetDelta target
    JoinDelta increment ->
      case older of
        ResetDelta target ->
          ResetDelta (joinValue target increment)
        JoinDelta priorIncrement ->
          JoinDelta (joinValue priorIncrement increment)

mapDelta ::
  (left -> right) ->
  Monotone left ->
  Monotone right
mapDelta f delta =
  case delta of
    JoinDelta value ->
      JoinDelta (f value)
    ResetDelta value ->
      ResetDelta (f value)