crdt-10.6: lib/CRDT/LamportClock/Simulation.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module CRDT.LamportClock.Simulation
(
-- * Lamport clock simulation
LamportClockSim
, LamportClockSimT (..)
, ObservedTime (..)
, ProcessSim
, ProcessSimT (..)
, evalProcessSim
, runLamportClockSim
, runLamportClockSimT
, runProcessSim
, runProcessSimT
) where
import Control.Monad.Except (ExceptT, MonadError, runExceptT,
throwError)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (ask)
import Control.Monad.RWS.Strict (RWST, evalRWST, tell)
import Control.Monad.State.Strict (StateT, evalState, evalStateT,
modify, state)
import Control.Monad.Trans (MonadTrans, lift)
import Data.Bifunctor (second)
import Data.Foldable (toList)
import Data.Functor.Identity (Identity)
import Data.Hashable (hash)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Numeric.Natural (Natural)
import CRDT.LamportClock (Clock, LamportTime (LamportTime), LocalTime,
Pid (Pid), Process, advance, getPid,
getTimes)
#if __GLASGOW_HASKELL__ < 800
import Compat ()
#endif /* __GLASGOW_HASKELL__ < 800 */
-- | Lamport clock simulation. Key is 'Pid'.
-- Non-present value is equivalent to (0, initial).
newtype LamportClockSimT m a =
LamportClockSim (ExceptT String (StateT (Map Pid LocalTime) m) a)
deriving (Applicative, Functor, Monad, MonadError String)
instance MonadTrans LamportClockSimT where
lift = LamportClockSim . lift . lift
instance Monad m => MonadFail (LamportClockSimT m) where
fail = throwError
instance MonadIO m => MonadIO (LamportClockSimT m) where
liftIO io = LamportClockSim $ liftIO io
type LamportClockSim = LamportClockSimT Identity
data ObservedTime = ObservedTime{stamp :: LocalTime, count :: Natural}
-- | ProcessSim inside Lamport clock simulation.
newtype ProcessSimT m a =
ProcessSim (RWST Pid (Seq ObservedTime) () (LamportClockSimT m) a)
deriving (Applicative, Functor, Monad, MonadFail)
type ProcessSim = ProcessSimT Identity
instance MonadTrans ProcessSimT where
lift = ProcessSim . lift . lift
instance Monad m => Process (ProcessSimT m) where
getPid = ProcessSim ask
instance Monad m => Clock (ProcessSimT m) where
getTimes n' = ProcessSim $ do
pid <- ask
time <- lift $ preIncreaseTime n pid
tell $ Seq.singleton ObservedTime{stamp = time, count = n}
pure $ LamportTime time pid
where
n = max n' 1
advance time = ProcessSim $ do
pid <- ask
lift . LamportClockSim . modify $ Map.alter (Just . advancePS) pid
where
advancePS = \case
Nothing -> time
Just current -> max time current
instance MonadIO m => MonadIO (ProcessSimT m) where
liftIO io = ProcessSim $ liftIO io
runLamportClockSim :: LamportClockSim a -> Either String a
runLamportClockSim (LamportClockSim action) =
evalState (runExceptT action) mempty
runLamportClockSimT :: Monad m => LamportClockSimT m a -> m (Either String a)
runLamportClockSimT (LamportClockSim action) =
evalStateT (runExceptT action) mempty
runProcessSim :: Pid -> ProcessSim a -> LamportClockSim a
runProcessSim = runProcessSimT
runProcessSimT :: Monad m => Pid -> ProcessSimT m a -> LamportClockSimT m a
runProcessSimT pid (ProcessSim action) = fst <$> evalRWST action pid ()
evalProcessSim :: Pid -> ProcessSim a -> LamportClockSim (a, [ObservedTime])
evalProcessSim pid (ProcessSim action) =
second toList <$> evalRWST action pid ()
-- | Increase time by pid and return new value
preIncreaseTime :: Monad m => Natural -> Pid -> LamportClockSimT m LocalTime
preIncreaseTime n pid = LamportClockSim $ state $ \pss ->
let time0 = fromMaybe 0 $ Map.lookup pid pss
Pid p = pid
d = fromIntegral . abs $ hash (time0, n, p)
time = time0 + max 1 d
in (time, Map.insert pid time pss)