packages feed

crdt-event-fold-1.4.0.0: src/Data/CRDT/EventFold/Monad.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wmissing-import-lists #-}

{- | Description: Monadic interaction with an EventFold. -}
module Data.CRDT.EventFold.Monad (
  MonadUpdateEF(..),
  MonadInspectEF(..),
  EventFoldT,
  runEventFoldT,
) where


import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Logger (MonadLogger, MonadLoggerIO)
import Control.Monad.Reader (MonadReader(ask), ReaderT(runReaderT))
import Control.Monad.State (MonadState(state), StateT, get, gets,
  runStateT)
import Control.Monad.Trans.Class (MonadTrans(lift))
import Data.CRDT.EventFold (Event(Output), UpdateResult(UpdateResult),
  Diff, EventFold, EventId, MergeError, urEventFold)
import qualified Data.CRDT.EventFold as EF (diffMerge, disassociate,
  event, fullMerge, participate)


{- |
  The interface for monadically updating an EventFold, where the
  monadic context is intended to manage:

  - The local participant.
  - The current state of the EventFold.
  - The accumulated consistent outputs.
  - Whether the 'EventFold' needs to be propagated to other participants.
-}
class MonadUpdateEF o p e m | m -> o p e where
  {- | Apply an event. See 'EF.event'. -}
  event :: e -> m (Output e, EventId p)

  {- | Perform a full merge. See 'EF.fullMerge'. -}
  fullMerge
    :: EventFold o p e
    -> m (Either (MergeError o p e) ())

  {- | Perform a diff merge. See 'EF.diffMerge'. -}
  diffMerge
    :: Diff o p e
    -> m (Either (MergeError o p e) ())

  {- | Allow a new participant to join in the cluster. See 'EF.participate'. -}
  participate :: p -> m (EventId p)

  {- | Remove a peer from participation. See 'EF.disassociate'. -}
  disassociate :: p -> m (EventId p)

  {- | Get the outstanding update results. -}
  getResult :: m (UpdateResult o p e)


{- |
  Interface for inspecting an Eventfold contained within the monadic
  context.
-}
class (Monad m) => MonadInspectEF o p e m | m -> o p e where
  efAsks :: (EventFold o p e -> a) -> m a
  efAsks f = f <$> efAsk

  efAsk :: m (EventFold o p e)
  efAsk = efAsks id


{- | A transformer providing 'MonadUpdateEF' and 'MonadInspectEF'. -}
newtype EventFoldT o p e m a = EventFoldT {
    unEventFoldT ::
      StateT (UpdateResult o p e) (
      ReaderT p m)
      a
  }
  deriving newtype
    ( Applicative
    , Functor
    , Monad
    , MonadIO
    , MonadLogger
    , MonadLoggerIO
    )
instance MonadTrans (EventFoldT o p e) where
  lift = EventFoldT . lift . lift

instance (Monad m) => MonadInspectEF o p e (EventFoldT o p e m) where
  efAsks f = EventFoldT $ gets (f . urEventFold)
  efAsk = EventFoldT $ gets urEventFold

instance
    ( Eq (Output e)
    , Eq e
    , Eq o
    , Event e
    , Monad m
    , Ord p
    )
  =>
    MonadUpdateEF o p e (EventFoldT o p e m)
  where
    event e =
      withEF
        (\ef self ->
          let (o, eid, ur) = EF.event self e ef
          in ((o, eid), ur)
        )

    fullMerge other =
      withEF
        (\ef self ->
          case EF.fullMerge self ef other of
            Left err -> (Left err, UpdateResult ef mempty False)
            Right ur -> (Right (), ur)
        )

    diffMerge diff =
      withEF
        (\ef self ->
          case EF.diffMerge self ef diff of
            Left err -> (Left err, UpdateResult ef mempty False)
            Right ur -> (Right (), ur)
        )

    participate participant =
      withEF (\ef self -> EF.participate self participant ef)

    disassociate participant =
      withEF (\ef _self -> EF.disassociate participant ef)

    getResult = EventFoldT get


{- |
  EventFoldT helper to make sure we always do the right thing when
  updating an event.
-}
withEF
  :: forall o p e m a. (Monad m, Ord p)
  => (EventFold o p e -> p -> (a, UpdateResult o p e))
  -> EventFoldT o p e m a
withEF f = EventFoldT $
    state . updateState =<< ask
  where
    updateState :: p -> UpdateResult o p e -> (a, UpdateResult o p e)
    updateState self (UpdateResult ef outputs prop) =
      let
        (a, UpdateResult ef2 outputs2 prop2) =
          f ef self
        results :: UpdateResult o p e
        results = UpdateResult ef2 (outputs <> outputs2) (prop || prop2)
      in
        (a, results)

runEventFoldT
  :: (Ord p)
  => p {- ^ The local participant. -}
  -> EventFold o p e {- ^ Initial event fold value.  -}
  -> EventFoldT o p e m a {- ^ The action to run.  -}
  -> m (a, UpdateResult o p e)
     {- ^
       Returns the result of the action, plus all the accumulated
       'UpdateResult's, which contain the new 'EventFold' value, all
       of the consistent outputs, and a flag indicating whether the new
       'EventFold' value should be propagated to the other participants.
     -}
runEventFoldT self ef = do
  flip runReaderT self
  . flip runStateT (UpdateResult ef mempty False)
  . unEventFoldT