module CRDT.LamportClock.Simulation
(
LamportClockSim
, LamportClockSimT (..)
, ProcessSim
, ProcessSimT (..)
, runLamportClockSim
, runLamportClockSimT
, runProcessSim
, runProcessSimT
) where
import Control.Monad.Except (ExceptT, MonadError, runExceptT,
throwError)
import Control.Monad.Fail (MonadFail, fail)
import Control.Monad.Reader (ReaderT, ask, runReaderT)
import Control.Monad.RWS.Strict (RWST, evalRWS, evalRWST)
import Control.Monad.State.Strict (MonadState, get, gets, modify, put,
state)
import Control.Monad.Trans (MonadTrans, lift)
import Data.Functor.Identity (Identity)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import CRDT.LamportClock (Clock, LamportTime (LamportTime), LocalTime,
Pid, Process, advance, getPid, getTime)
newtype LamportClockSimT s m a =
LamportClockSim (ExceptT String (RWST s () (Map Pid (ProcessState s)) m) a)
deriving (Applicative, Functor, Monad, MonadError String)
instance MonadTrans (LamportClockSimT s) where
lift = LamportClockSim . lift . lift
instance Monad m => MonadFail (LamportClockSimT s m) where
fail = throwError
type LamportClockSim s = LamportClockSimT s Identity
data ProcessState s = ProcessState
{ time :: LocalTime
, var :: s
}
newtype ProcessSimT s m a = ProcessSim (ReaderT Pid (LamportClockSimT s m) a)
deriving (Applicative, Functor, Monad, MonadFail)
type ProcessSim s = ProcessSimT s Identity
instance MonadTrans (ProcessSimT s) where
lift = ProcessSim . lift . lift
instance Monad m => Process (ProcessSimT s m) where
getPid = ProcessSim ask
instance Monad m => Clock (ProcessSimT s m) where
getTime = ProcessSim $ do
pid <- ask
time <- lift $ preIncrementTime pid
pure $ LamportTime time pid
advance time = ProcessSim $ do
pid <- ask
lift . LamportClockSim $ do
initial <- ask
modify $ Map.alter (Just . advancePS initial) pid
where
advancePS initial Nothing = ProcessState{time, var = initial}
advancePS _ (Just ProcessState{time = current, var}) =
ProcessState{time = max time current, var}
instance Monad m => MonadState s (ProcessSimT s m) where
get = ProcessSim $ do
pid <- ask
lift . LamportClockSim $ do
initial <- ask
gets $ maybe initial var . Map.lookup pid
put var = ProcessSim $ do
pid <- ask
lift . LamportClockSim . modify $ Map.alter (Just . putPS) pid
where
putPS Nothing = ProcessState{time = 0, var}
putPS (Just ProcessState{time}) = ProcessState{time, var}
runLamportClockSim :: s -> LamportClockSim s a -> Either String a
runLamportClockSim initial (LamportClockSim action) =
fst $ evalRWS (runExceptT action) initial mempty
runLamportClockSimT
:: Monad m => s -> LamportClockSimT s m a -> m (Either String a)
runLamportClockSimT initial (LamportClockSim action) =
fst <$> evalRWST (runExceptT action) initial mempty
runProcessSim :: Pid -> ProcessSim s a -> LamportClockSim s a
runProcessSim pid (ProcessSim action) = runReaderT action pid
runProcessSimT :: Pid -> ProcessSimT s m a -> LamportClockSimT s m a
runProcessSimT pid (ProcessSim action) = runReaderT action pid
preIncrementTime :: Monad m => Pid -> LamportClockSimT s m LocalTime
preIncrementTime pid = LamportClockSim $ do
initial <- ask
state $ \pss -> let
ps@ProcessState{time} =
case Map.lookup pid pss of
Nothing -> ProcessState{time = 1, var = initial}
Just ProcessState{time = current, var} ->
ProcessState{time = succ current, var}
in
(time, Map.insert pid ps pss)