packages feed

crdt 6.2 → 7.0

raw patch · 26 files changed

+992/−180 lines, 26 filesdep +Diffdep +QuickCheck-GenTdep +vectordep ~containersPVP ok

version bump matches the API change (PVP)

Dependencies added: Diff, QuickCheck-GenT, vector

Dependency ranges changed: containers

API changes (from Hackage documentation)

- CRDT.Cm.GSet: instance GHC.Classes.Eq a => CRDT.Cm.CausalOrd (CRDT.Cm.GSet.GSet a)
- CRDT.Cv.TwoPSet: lookup :: Ord a => a -> TwoPSet a -> Bool
- CRDT.LWW: initial :: Clock m => a -> m (LWW a)
- CRDT.LamportClock: LamportClockSim :: (State (Map Pid LocalTime) a) -> LamportClockSim a
- CRDT.LamportClock: ProcessSim :: (ReaderT Pid LamportClockSim a) -> ProcessSim a
- CRDT.LamportClock: instance CRDT.LamportClock.Clock CRDT.LamportClock.ProcessSim
- CRDT.LamportClock: instance CRDT.LamportClock.Process CRDT.LamportClock.ProcessSim
- CRDT.LamportClock: instance GHC.Base.Applicative CRDT.LamportClock.LamportClockSim
- CRDT.LamportClock: instance GHC.Base.Applicative CRDT.LamportClock.ProcessSim
- CRDT.LamportClock: instance GHC.Base.Functor CRDT.LamportClock.LamportClockSim
- CRDT.LamportClock: instance GHC.Base.Functor CRDT.LamportClock.ProcessSim
- CRDT.LamportClock: instance GHC.Base.Monad CRDT.LamportClock.LamportClockSim
- CRDT.LamportClock: instance GHC.Base.Monad CRDT.LamportClock.ProcessSim
- CRDT.LamportClock: newtype LamportClockSim a
- CRDT.LamportClock: newtype ProcessSim a
- CRDT.LamportClock: runLamportClockSim :: LamportClockSim a -> a
- CRDT.LamportClock: runProcessSim :: Pid -> ProcessSim a -> LamportClockSim a
+ CRDT.Cm: initial :: CmRDT op => Payload op
+ CRDT.Cm: makeAndApplyOp :: (CmRDT op, Clock m, MonadFail m, MonadState (Payload op) m) => Intent op -> m op
+ CRDT.Cm: makeAndApplyOps :: (CmRDT op, Clock m, MonadFail m, MonadState (Payload op) m, Traversable f) => f (Intent op) -> m (f op)
+ CRDT.Cm: query :: forall op f. (CmRDT op, Foldable f) => f op -> Payload op
+ CRDT.Cm.GSet: instance CRDT.Cm.CausalOrd (CRDT.Cm.GSet.GSet a)
+ CRDT.Cm.ORSet: Add :: a -> Intent a
+ CRDT.Cm.ORSet: OpAdd :: a -> Tag -> ORSet a
+ CRDT.Cm.ORSet: OpRemove :: a -> (Set Tag) -> ORSet a
+ CRDT.Cm.ORSet: Payload :: MultiMap a Tag -> Version -> Payload a
+ CRDT.Cm.ORSet: Remove :: a -> Intent a
+ CRDT.Cm.ORSet: Tag :: Pid -> Version -> Tag
+ CRDT.Cm.ORSet: [elements] :: Payload a -> MultiMap a Tag
+ CRDT.Cm.ORSet: [version] :: Payload a -> Version
+ CRDT.Cm.ORSet: data Intent a
+ CRDT.Cm.ORSet: data ORSet a
+ CRDT.Cm.ORSet: data Payload a
+ CRDT.Cm.ORSet: data Tag
+ CRDT.Cm.ORSet: instance CRDT.Cm.CausalOrd (CRDT.Cm.ORSet.ORSet a)
+ CRDT.Cm.ORSet: instance GHC.Classes.Eq CRDT.Cm.ORSet.Tag
+ CRDT.Cm.ORSet: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.ORSet.Payload a)
+ CRDT.Cm.ORSet: instance GHC.Classes.Ord CRDT.Cm.ORSet.Tag
+ CRDT.Cm.ORSet: instance GHC.Classes.Ord a => CRDT.Cm.CmRDT (CRDT.Cm.ORSet.ORSet a)
+ CRDT.Cm.ORSet: instance GHC.Show.Show CRDT.Cm.ORSet.Tag
+ CRDT.Cm.ORSet: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.ORSet.Intent a)
+ CRDT.Cm.ORSet: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.ORSet.ORSet a)
+ CRDT.Cm.ORSet: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.ORSet.Payload a)
+ CRDT.Cm.ORSet: query :: (Ord a, Foldable f) => f (ORSet a) -> Set a
+ CRDT.Cm.RGA: AddAfter :: (Maybe VertexId) -> a -> RgaIntent a
+ CRDT.Cm.RGA: OpAddAfter :: (Maybe VertexId) -> a -> VertexId -> RGA a
+ CRDT.Cm.RGA: OpRemove :: VertexId -> RGA a
+ CRDT.Cm.RGA: Remove :: VertexId -> RgaIntent a
+ CRDT.Cm.RGA: RgaPayload :: Vector (VertexId, Maybe a) -> Map VertexId Int -> RgaPayload a
+ CRDT.Cm.RGA: [vertexIxs] :: RgaPayload a -> Map VertexId Int
+ CRDT.Cm.RGA: [vertices] :: RgaPayload a -> Vector (VertexId, Maybe a)
+ CRDT.Cm.RGA: data RGA a
+ CRDT.Cm.RGA: data RgaIntent a
+ CRDT.Cm.RGA: data RgaPayload a
+ CRDT.Cm.RGA: fromString :: (Clock m, MonadFail m, MonadState (RgaPayload Char) m) => String -> m [RGA Char]
+ CRDT.Cm.RGA: instance CRDT.Cm.CausalOrd (CRDT.Cm.RGA.RGA a)
+ CRDT.Cm.RGA: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.RGA.RGA a)
+ CRDT.Cm.RGA: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.RGA.RgaPayload a)
+ CRDT.Cm.RGA: instance GHC.Classes.Ord a => CRDT.Cm.CmRDT (CRDT.Cm.RGA.RGA a)
+ CRDT.Cm.RGA: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.RGA.RGA a)
+ CRDT.Cm.RGA: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.RGA.RgaIntent a)
+ CRDT.Cm.RGA: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.RGA.RgaPayload a)
+ CRDT.Cm.RGA: load :: Vector (VertexId, Maybe a) -> RgaPayload a
+ CRDT.Cm.RGA: toString :: RgaPayload Char -> String
+ CRDT.Cm.RGA: toVector :: RgaPayload a -> Vector a
+ CRDT.Cv.RGA: RGA :: [(VertexId, Maybe a)] -> RGA a
+ CRDT.Cv.RGA: edit :: (Eq a, Clock m) => [a] -> RGA a -> m (RGA a)
+ CRDT.Cv.RGA: fromList :: Clock m => [a] -> m (RGA a)
+ CRDT.Cv.RGA: fromString :: Clock m => String -> m RgaString
+ CRDT.Cv.RGA: instance GHC.Classes.Eq a => Data.Semigroup.Semigroup (CRDT.Cv.RGA.RGA a)
+ CRDT.Cv.RGA: instance GHC.Classes.Eq a => Data.Semilattice.Semilattice (CRDT.Cv.RGA.RGA a)
+ CRDT.Cv.RGA: instance GHC.Classes.Eq a => GHC.Base.Monoid (CRDT.Cv.RGA.RGA a)
+ CRDT.Cv.RGA: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cv.RGA.RGA a)
+ CRDT.Cv.RGA: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cv.RGA.RGA a)
+ CRDT.Cv.RGA: newtype RGA a
+ CRDT.Cv.RGA: pack :: RGA a -> RgaPacked a
+ CRDT.Cv.RGA: toList :: RGA a -> [a]
+ CRDT.Cv.RGA: toString :: RgaString -> String
+ CRDT.Cv.RGA: type RgaPacked a = [(VertexId, [Maybe a])]
+ CRDT.Cv.RGA: type RgaString = RGA Char
+ CRDT.Cv.RGA: unpack :: RgaPacked a -> RGA a
+ CRDT.Cv.TwoPSet: member :: Ord a => a -> TwoPSet a -> Bool
+ CRDT.LWW: initialize :: Clock m => a -> m (LWW a)
+ CRDT.LamportClock.Simulation: LamportClockSim :: (ExceptT String (RWST s () (Map Pid (ProcessState s)) m) a) -> LamportClockSimT s m a
+ CRDT.LamportClock.Simulation: ProcessSim :: (ReaderT Pid (LamportClockSimT s m) a) -> ProcessSimT s m a
+ CRDT.LamportClock.Simulation: instance Control.Monad.Trans.Class.MonadTrans (CRDT.LamportClock.Simulation.LamportClockSimT s)
+ CRDT.LamportClock.Simulation: instance Control.Monad.Trans.Class.MonadTrans (CRDT.LamportClock.Simulation.ProcessSimT s)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Functor m => GHC.Base.Functor (CRDT.LamportClock.Simulation.LamportClockSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Functor m => GHC.Base.Functor (CRDT.LamportClock.Simulation.ProcessSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => CRDT.LamportClock.Clock (CRDT.LamportClock.Simulation.ProcessSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => CRDT.LamportClock.Process (CRDT.LamportClock.Simulation.ProcessSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => Control.Monad.Error.Class.MonadError GHC.Base.String (CRDT.LamportClock.Simulation.LamportClockSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => Control.Monad.Fail.MonadFail (CRDT.LamportClock.Simulation.LamportClockSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => Control.Monad.Fail.MonadFail (CRDT.LamportClock.Simulation.ProcessSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => Control.Monad.State.Class.MonadState s (CRDT.LamportClock.Simulation.ProcessSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => GHC.Base.Applicative (CRDT.LamportClock.Simulation.LamportClockSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => GHC.Base.Applicative (CRDT.LamportClock.Simulation.ProcessSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => GHC.Base.Monad (CRDT.LamportClock.Simulation.LamportClockSimT s m)
+ CRDT.LamportClock.Simulation: instance GHC.Base.Monad m => GHC.Base.Monad (CRDT.LamportClock.Simulation.ProcessSimT s m)
+ CRDT.LamportClock.Simulation: newtype LamportClockSimT s m a
+ CRDT.LamportClock.Simulation: newtype ProcessSimT s m a
+ CRDT.LamportClock.Simulation: runLamportClockSim :: s -> LamportClockSim s a -> Either String a
+ CRDT.LamportClock.Simulation: runLamportClockSimT :: Monad m => s -> LamportClockSimT s m a -> m (Either String a)
+ CRDT.LamportClock.Simulation: runProcessSim :: Pid -> ProcessSim s a -> LamportClockSim s a
+ CRDT.LamportClock.Simulation: runProcessSimT :: Pid -> ProcessSimT s m a -> LamportClockSimT s m a
+ CRDT.LamportClock.Simulation: type LamportClockSim s = LamportClockSimT s Identity
+ CRDT.LamportClock.Simulation: type ProcessSim s = ProcessSimT s Identity
+ Data.MultiMap: MultiMap :: (Map k (Set v)) -> MultiMap k v
+ Data.MultiMap: assocs :: MultiMap k v -> [(k, [v])]
+ Data.MultiMap: delete :: (Ord k, Ord v) => k -> v -> MultiMap k v -> MultiMap k v
+ Data.MultiMap: deleteMany :: (Ord k, Ord v) => k -> Set v -> MultiMap k v -> MultiMap k v
+ Data.MultiMap: empty :: MultiMap k v
+ Data.MultiMap: insert :: (Ord k, Ord v) => k -> v -> MultiMap k v -> MultiMap k v
+ Data.MultiMap: instance (GHC.Classes.Eq v, GHC.Classes.Eq k) => GHC.Classes.Eq (Data.MultiMap.MultiMap k v)
+ Data.MultiMap: instance (GHC.Show.Show v, GHC.Show.Show k) => GHC.Show.Show (Data.MultiMap.MultiMap k v)
+ Data.MultiMap: keysSet :: MultiMap k v -> Set k
+ Data.MultiMap: lookup :: Ord k => k -> MultiMap k v -> Set v
+ Data.MultiMap: newtype MultiMap k v
+ Data.MultiMap: singleton :: k -> v -> MultiMap k v
- CRDT.LamportClock: LamportTime :: !LocalTime -> !Pid -> LamportTime
+ CRDT.LamportClock: LamportTime :: LocalTime -> Pid -> LamportTime

Files

crdt.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 7021f3e0d6e0f14ccbff42fd8252b25b479ffd645160458d77d1c854912c9698+-- hash: 03da394dee75b1bb3cc8d9b26448adbfe3b32c0f4f91c1f62c6672bb5ab17d04  name:           crdt-version:        6.2+version:        7.0 synopsis:       Conflict-free replicated data types description:    Definitions of CmRDT and CvRDT. Implementations for some classic CRDTs. category:       Distributed Systems@@ -26,7 +26,8 @@   hs-source-dirs:       lib   build-depends:-      base >=4.9 && <4.11+      Diff+    , base >=4.9 && <4.11     , binary     , bytestring     , containers@@ -35,10 +36,13 @@     , safe     , stm     , time+    , vector   exposed-modules:       CRDT.Cm       CRDT.Cm.Counter       CRDT.Cm.GSet+      CRDT.Cm.ORSet+      CRDT.Cm.RGA       CRDT.Cm.TwoPSet       CRDT.Cv       CRDT.Cv.GCounter@@ -47,9 +51,12 @@       CRDT.Cv.Max       CRDT.Cv.ORSet       CRDT.Cv.PNCounter+      CRDT.Cv.RGA       CRDT.Cv.TwoPSet       CRDT.LamportClock+      CRDT.LamportClock.Simulation       CRDT.LWW+      Data.MultiMap       Data.Semilattice   other-modules:       Paths_crdt@@ -62,17 +69,23 @@       test   build-depends:       QuickCheck+    , QuickCheck-GenT     , base >=4.9 && <4.11     , containers     , crdt+    , mtl     , quickcheck-instances     , tasty     , tasty-discover >=4.1     , tasty-quickcheck+    , vector   other-modules:       ArbitraryOrphans+      Cm.ORSet       Cm.TwoPSet       Counter+      Cv.ORSet+      Cv.RGA       Cv.TwoPSet       GCounter       GSet@@ -80,7 +93,8 @@       LWW       LwwElementSet       Max-      ORSet       PNCounter+      RGA+      Util       Paths_crdt   default-language: Haskell2010
lib/CRDT/Cm.hs view
@@ -1,13 +1,24 @@+{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}  module CRDT.Cm     ( CausalOrd (..)     , CmRDT (..)     , concurrent+    , query+    , makeAndApplyOp+    , makeAndApplyOps     ) where +import           Prelude hiding (fail)++import           Control.Monad.Fail (MonadFail, fail)+import           Control.Monad.State.Strict (MonadState, get, modify)+ import           CRDT.LamportClock (Clock)  -- | Partial order for causal semantics.@@ -61,6 +72,8 @@      type Payload op +    initial :: Payload op+     -- | Generate an update to the local and remote replicas.     --     -- Returns 'Nothing' if the intended operation is not applicable.@@ -77,3 +90,27 @@     -- TODO(Syrovetsky, 2017-12-05) There is no downstream precondition yet.     -- We must make a test for it first.     apply :: op -> Payload op -> Payload op++query :: forall op f. (CmRDT op, Foldable f) => f op -> Payload op+query = foldl (flip apply) (initial @op)++-- | Make op and apply it to the payload -- a common routine at the source node.+makeAndApplyOp+    :: (CmRDT op, Clock m, MonadFail m, MonadState (Payload op) m)+    => Intent op -> m op+makeAndApplyOp intent = do+    payload <- get+    case makeOp intent payload of+        Nothing -> fail "precodition failed"+        Just opAction -> do+            op <- opAction+            modify $ apply op+            pure op++makeAndApplyOps+    :: ( CmRDT op+       , Clock m, MonadFail m, MonadState (Payload op) m+       , Traversable f+       )+    => f (Intent op) -> m (f op)+makeAndApplyOps = traverse makeAndApplyOp
lib/CRDT/Cm/Counter.hs view
@@ -15,6 +15,8 @@ instance (Num a, Eq a) => CmRDT (Counter a) where     type Payload (Counter a) = a +    initial = 0+     apply = \case         Increment -> (+ 1)         Decrement -> subtract 1
lib/CRDT/Cm/GSet.hs view
@@ -15,7 +15,9 @@ instance Ord a => CmRDT (GSet a) where     type Payload (GSet a) = Set a +    initial = Set.empty+     apply (Add a) = Set.insert a -instance Eq a => CausalOrd (GSet a) where+instance CausalOrd (GSet a) where     precedes _ _ = False
+ lib/CRDT/Cm/ORSet.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}++module CRDT.Cm.ORSet+    ( ORSet (..)+    , Intent (..)+    , Payload (..)+    , Tag (..)+    , query+    ) where++import           Data.MultiMap (MultiMap)+import qualified Data.MultiMap as MultiMap+import           Data.Set (Set)+import           Numeric.Natural (Natural)++import           CRDT.Cm (CausalOrd, CmRDT)+import qualified CRDT.Cm as Cm+import           CRDT.LamportClock (Pid (Pid), getPid)++data ORSet a = OpAdd a Tag | OpRemove a (Set Tag)+    deriving Show++data Intent a = Add a | Remove a+    deriving Show++data Payload a = Payload+    { elements :: MultiMap a Tag+    , version  :: Version+    }+    deriving (Eq, Show)++data Tag = Tag Pid Version+    deriving (Eq, Ord)++type Version = Natural++instance Show Tag where+    show (Tag (Pid pid) version) = show pid ++ '-' : show version++instance CausalOrd (ORSet a) where+    precedes _ _ = False++instance Ord a => CmRDT (ORSet a) where+    type Intent  (ORSet a) = Intent  a+    type Payload (ORSet a) = Payload a++    initial = Payload{elements = MultiMap.empty, version = 0}++    makeOp (Add a) Payload{version} = Just $ do+        pid <- getPid+        pure $ OpAdd a $ Tag pid version+    makeOp (Remove a) Payload{elements} =+        Just . pure . OpRemove a $ MultiMap.lookup a elements++    apply op Payload{elements, version} = Payload+        { version  = version + 1+        , elements = case op of+            OpAdd    a tag  -> MultiMap.insert     a tag  elements+            OpRemove a tags -> MultiMap.deleteMany a tags elements+        }++query :: (Ord a, Foldable f) => f (ORSet a) -> Set a+query = MultiMap.keysSet . elements . Cm.query
+ lib/CRDT/Cm/RGA.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE TypeFamilies #-}++-- | Replicated Growable Array (RGA)+module CRDT.Cm.RGA+    ( RGA (..)+    , RgaIntent (..)+    , RgaPayload (..)+    , fromString+    , load+    , toString+    , toVector+    ) where++import           Prelude hiding (lookup)++import           Control.Monad.Fail (MonadFail)+import           Control.Monad.State.Strict (MonadState)+import           Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import           Data.Semigroup ((<>))+import           Data.Vector (Vector, (//))+import qualified Data.Vector as Vector++import           CRDT.Cm (CausalOrd, CmRDT, Intent, Payload, apply, initial,+                          makeAndApplyOp, makeOp, precedes)+import           CRDT.LamportClock (Clock, LamportTime (LamportTime), advance,+                                    getTime)++-- | Using 'LamportTime' as an identifier for vertices+type VertexId = LamportTime++data RgaPayload a = RgaPayload+    { vertices  :: Vector (VertexId, Maybe a) -- TODO(cblp, 2018-02-06) Unbox+    , vertexIxs :: Map VertexId Int+      -- ^ indices in `vertices` vector+    }+    deriving (Eq, Show)++-- | Is added and is not removed.+lookup :: VertexId -> RgaPayload a -> Bool+lookup v RgaPayload{vertices, vertexIxs} =+    case Map.lookup v vertexIxs of+        Just ix -> case vertices Vector.! ix of+            (_, Just _) -> True+            _           -> False+        Nothing -> False++-- before :: Vertex -> Vertex -> Bool+-- before u v = b+--     pre lookup(u) ∧ lookup(v)+--     ∃w_1, ..., w_m ∈ verticesAdded:+--         w_1 = u+--         ∧ w_m = v+--         ∧ ∀j: (w_j, w_{j+1}) ∈ edges++data RgaIntent a+    = AddAfter (Maybe VertexId) a+      -- ^ 'Nothing' means the beginning+    | Remove VertexId+    deriving (Show)++data RGA a+    = OpAddAfter (Maybe VertexId) a VertexId+      -- ^ - id of previous vertex, 'Nothing' means the beginning+      --   - atom+      --   - id of this vertex+    | OpRemove VertexId+    deriving (Eq, Show)++instance CausalOrd (RGA a) where+    precedes _ _ = False++emptyPayload :: RgaPayload a+emptyPayload = RgaPayload+    { vertices = Vector.empty+    , vertexIxs = Map.empty+    }++instance Ord a => CmRDT (RGA a) where+    type Intent  (RGA a) = RgaIntent  a+    type Payload (RGA a) = RgaPayload a++    initial = emptyPayload++    makeOp (AddAfter mOldId atom) payload = case mOldId of+        Nothing -> ok+        Just oldId+            | lookup oldId payload -> ok+            | otherwise            -> Nothing+      where+        RgaPayload{vertexIxs} = payload+        ok = Just $ do+            case Map.lookupMax vertexIxs of+                Just (LamportTime maxKnownTime _, _) ->+                    advance maxKnownTime+                Nothing -> pure ()+            newId <- getTime+            pure $ OpAddAfter mOldId atom newId++    makeOp (Remove w) payload+        | lookup w payload = Just . pure $ OpRemove w+        | otherwise        = Nothing++    apply (OpAddAfter mOldId newAtom newId) payload =+        RgaPayload+            { vertices  = vertices'+            , vertexIxs = vertexIxs'+            }+      where+        RgaPayload{vertices, vertexIxs} = payload+        n = length vertices++        (vertices', newIx)+            | null vertices = case mOldId of+                Nothing    -> (Vector.singleton (newId, Just newAtom), 0)+                Just oldId -> error $ show oldId <> " not delivered"+            | otherwise = (insert ix, ix)+              where+                ix = findWhereToInsert $ case mOldId of+                    Nothing    -> 0+                    Just oldId -> vertexIxs Map.! oldId + 1++        vertexIxs' = Map.insert newId newIx $ Map.map shift vertexIxs++        shift ix+            | ix >= newIx = ix + 1+            | otherwise   = ix++        -- Find an edge (l, r) within which to splice new+        findWhereToInsert ix =+            case vertices Vector.!? ix of+                Just (t', _) | newId < t' -> -- Right position, wrong order+                    findWhereToInsert $ succ ix+                _ -> ix++        insert ix+            | ix < n = left <> Vector.singleton (newId, Just newAtom) <> right+            | otherwise = Vector.snoc vertices (newId, Just newAtom)+          where+            (left, right) = Vector.splitAt ix vertices++    apply (OpRemove vid) payload@RgaPayload{vertices, vertexIxs} =+        -- pre addAfter(_, w) delivered  -- 2P-Set precondition+        payload{vertices = vertices // [(ix, (vid, Nothing))]}+      where+        ix = vertexIxs Map.! vid++fromList+    :: (Ord a, Clock m, MonadFail m, MonadState (RgaPayload a) m)+    => [a] -> m [RGA a]+fromList = go Nothing+  where+    go _      []     = pure []+    go prevId (x:xs) = do+        op@(OpAddAfter _ _ newId) <- makeAndApplyOp (AddAfter prevId x)+        (op :) <$> go (Just newId) xs++toList :: RgaPayload a -> [a]+toList RgaPayload{vertices} = [a | (_, Just a) <- Vector.toList vertices]++toVector :: RgaPayload a -> Vector a+toVector RgaPayload{vertices} = Vector.mapMaybe snd vertices++fromString+    :: (Clock m, MonadFail m, MonadState (RgaPayload Char) m)+    => String -> m [RGA Char]+fromString = fromList++toString :: RgaPayload Char -> String+toString = toList++load :: Vector (VertexId, Maybe a) -> RgaPayload a+load vertices = RgaPayload+    { vertices+    , vertexIxs = Map.fromList+        [(vid, ix) | ix <- [0..] | (vid, _) <- Vector.toList vertices]+    }
lib/CRDT/Cm/TwoPSet.hs view
@@ -6,9 +6,8 @@     ( TwoPSet (..)     ) where -import           Control.Monad (guard)-import           Data.Set (Set)-import qualified Data.Set as Set+import           Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map  import           CRDT.Cm (CausalOrd (..), CmRDT (..)) @@ -16,15 +15,21 @@     deriving (Eq, Show)  instance Ord a => CmRDT (TwoPSet a) where-    type Payload (TwoPSet a) = Set a+    type Payload (TwoPSet a) = Map a Bool -    makeOp op s = case op of-        Add _     -> Just (pure op)-        Remove a  -> guard (Set.member a s) *> Just (pure op)+    initial = Map.empty +    makeOp op payload = case op of+        Add    _ -> Just $ pure op+        Remove a+            | isKnown a -> Just $ pure op+            | otherwise -> Nothing+      where+        isKnown a = Map.member a payload+     apply = \case-        Add a     -> Set.insert a-        Remove a  -> Set.delete a+        Add    a -> Map.insertWith (&&) a True+        Remove a -> Map.insert          a False  instance Eq a => CausalOrd (TwoPSet a) where     Add b `precedes` Remove a = a == b -- `Remove e` can occur only after `Add e`
lib/CRDT/Cv/LwwElementSet.hs view
@@ -35,13 +35,13 @@ add :: (Ord a, Clock m) => a -> LwwElementSet a -> m (LwwElementSet a) add value old@(LES m) = do     advanceFromLES old-    tag <- LWW.initial True+    tag <- LWW.initialize True     pure . LES $ Map.insertWith (<>) value tag m  remove :: (Ord a, Clock m) => a -> LwwElementSet a -> m (LwwElementSet a) remove value old@(LES m) = do     advanceFromLES old-    tag <- LWW.initial False+    tag <- LWW.initialize False     pure . LES $ Map.insertWith (<>) value tag m  lookup :: Ord a => a -> LwwElementSet a -> Bool
+ lib/CRDT/Cv/RGA.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE TupleSections #-}++module CRDT.Cv.RGA+    ( RGA (..)+    , fromList+    , toList+    , edit+    , RgaString+    , fromString+    , toString+    -- * Packed representation+    , RgaPacked+    , pack+    , unpack+    ) where++import           Data.Algorithm.Diff (Diff (Both, First, Second), getDiffBy)+import           Data.Function (on)+import           Data.Semigroup (Semigroup, (<>))+import           Data.Semilattice (Semilattice)+import           Data.Traversable (for)++import           CRDT.LamportClock (Clock, LamportTime (LamportTime), getTime)++type VertexId = LamportTime++-- | TODO(cblp, 2018-02-06) Vector.Unboxed+newtype RGA a = RGA [(VertexId, Maybe a)]+    deriving (Eq, Show)++type RgaString = RGA Char++merge :: Eq a => RGA a -> RGA a -> RGA a+merge (RGA vertices1) (RGA vertices2) =+    RGA $ mergeVertexLists vertices1 vertices2+  where+    mergeVertexLists []                   vs2                  = vs2+    mergeVertexLists vs1                  []                   = vs1+    mergeVertexLists (v1@(id1, a1) : vs1) (v2@(id2, a2) : vs2) =+        case compare id1 id2 of+            LT -> v2                      : mergeVertexLists (v1:vs1) vs2+            GT -> v1                      : mergeVertexLists vs1      (v2:vs2)+            EQ -> (id1, mergeAtoms a1 a2) : mergeVertexLists vs1      vs2++    mergeAtoms (Just a1) (Just a2)+        | a1 == a2 = Just a1+    mergeAtoms _ _ = Nothing++instance Eq a => Semigroup (RGA a) where+    (<>) = merge++instance Eq a => Semilattice (RGA a)++-- Why not?+instance Eq a => Monoid (RGA a) where+    mempty = RGA []+    mappend = (<>)++toList :: RGA a -> [a]+toList (RGA rga) = [a | (_, Just a) <- rga]++toString :: RgaString -> String+toString = toList++fromList :: Clock m => [a] -> m (RGA a)+fromList = fmap RGA . traverse makeVertex+  where+    makeVertex a = do+        t <- getTime+        pure (t, Just a)++fromString :: Clock m => String -> m RgaString+fromString = fromList++-- | Replace content with specified,+-- applying changed found by the diff algorithm+edit :: (Eq a, Clock m) => [a] -> RGA a -> m (RGA a)+edit newList (RGA oldRga) =+    fmap RGA $ for diff $ \case+        First  (vid, _)        -> pure (vid, Nothing)+        Both   v        _      -> pure v+        Second          (_, a) -> (, a) <$> getTime+  where+    newList' = [(undefined, Just a) | a <- newList]+    diff = getDiffBy ((==) `on` snd) oldRga newList' -- TODO(cblp, 2018-02-07) getGroupedDiffBy++-- | Compact version of 'RGA'.+-- For each 'VertexId', the corresponding sequence of vetices has the same 'Pid'+-- and sequentially growing 'LocalTime', starting with the specified one.+type RgaPacked a = [(VertexId, [Maybe a])]++pack :: RGA a -> RgaPacked a+pack (RGA []) = []+pack (RGA ((first, atom):vs)) = go first [atom] 1 vs+  where+    -- TODO(cblp, 2018-02-08) buf :: DList+    go vid buf _ [] = [(vid, buf)]+    go vid buf dt ((wid, a):ws)+        | wid == next dt vid = go vid (buf ++ [a]) (succ dt) ws+        | otherwise = (vid, buf) : go wid [a] 1 ws+    next dt (LamportTime t p) = LamportTime (t + dt) p++unpack :: RgaPacked a -> RGA a+unpack packed = RGA $ do+    (LamportTime time pid, atoms) <- packed+    [(LamportTime (time + i) pid, atom) | i <- [0..] | atom <- atoms]
lib/CRDT/Cv/TwoPSet.hs view
@@ -2,14 +2,12 @@     ( TwoPSet (..)     , add     , initial-    , lookup+    , member     , remove     , singleton     , isKnown     ) where -import           Prelude hiding (lookup)- import           Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import           Data.Maybe (fromMaybe)@@ -31,8 +29,8 @@ initial :: TwoPSet a initial = TwoPSet Map.empty -lookup :: Ord a => a -> TwoPSet a -> Bool-lookup e (TwoPSet m) = fromMaybe False $ Map.lookup e m+member :: Ord a => a -> TwoPSet a -> Bool+member e (TwoPSet m) = fromMaybe False $ Map.lookup e m  remove :: Ord a => a -> TwoPSet a -> TwoPSet a remove e (TwoPSet m) = TwoPSet $ Map.adjust (const False) e m@@ -40,6 +38,6 @@ singleton :: Ord a => a -> TwoPSet a singleton a = add a initial --- | XXX Internal TODO remove+-- | XXX Internal isKnown :: Ord a => a -> TwoPSet a -> Bool isKnown e (TwoPSet m) = Map.member e m
lib/CRDT/LWW.hs view
@@ -1,10 +1,11 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TypeFamilies #-}  module CRDT.LWW     ( LWW (..)       -- * CvRDT-    , initial+    , initialize     , assign     , query       -- * Implementation detail@@ -47,15 +48,15 @@ instance Eq a => Semilattice (LWW a)  -- | Initialize state-initial :: Clock m => a -> m (LWW a)-initial value = LWW value <$> getTime+initialize :: Clock m => a -> m (LWW a)+initialize value = LWW value <$> getTime  -- | Change state as CvRDT operation. -- Current value is ignored, because new timestamp is always greater. assign :: Clock m => a -> LWW a -> m (LWW a) assign value old = do     advanceFromLWW old-    initial value+    initialize value  -- | Query state query :: LWW a -> a@@ -69,11 +70,17 @@  instance Eq a => CmRDT (LWW a) where     type Intent  (LWW a) = a-    type Payload (LWW a) = LWW a+    type Payload (LWW a) = Maybe (LWW a) -    makeOp value = Just . assign value+    initial = Nothing -    apply = (<>)+    makeOp value = Just . \case+        Just payload -> assign value payload+        Nothing      -> initialize value++    apply op = Just . \case+        Just payload -> op <> payload+        Nothing      -> op  advanceFromLWW :: Clock m => LWW a -> m () advanceFromLWW LWW{time = LamportTime time _} = advance time
lib/CRDT/LamportClock.hs view
@@ -1,4 +1,7 @@+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE StrictData #-}  module CRDT.LamportClock     ( Pid (..)@@ -7,11 +10,6 @@     , LamportTime (..)     , LocalTime     , Process (..)-    -- * Lamport clock simulation-    , LamportClockSim (..)-    , ProcessSim (..)-    , runLamportClockSim-    , runProcessSim     -- * Real Lamport clock     , LamportClock     , runLamportClock@@ -23,13 +21,9 @@                                          readTVar, writeTVar) import           Control.Monad.IO.Class (MonadIO, liftIO) import           Control.Monad.Reader (ReaderT, ask, runReaderT)-import           Control.Monad.State.Strict (State, evalState, modify, state) import           Control.Monad.Trans (lift) import           Data.Binary (decode) import qualified Data.ByteString.Lazy as BSL-import           Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import           Data.Maybe (fromMaybe) import           Data.Time.Clock.POSIX (getPOSIXTime) import           Data.Word (Word64) import           Network.Info (MAC (MAC), getNetworkInterfaces, mac)@@ -39,37 +33,19 @@ -- | Unix time in 10^{-7} seconds (100 ns), as in RFC 4122 and Swarm RON. type LocalTime = Natural -data LamportTime = LamportTime !LocalTime !Pid-    deriving (Eq, Ord, Show)+data LamportTime = LamportTime LocalTime Pid+    deriving (Eq, Ord) +instance Show LamportTime where+    show (LamportTime time (Pid pid)) = show time ++ '.' : show pid+ -- | Unique process identifier newtype Pid = Pid Word64     deriving (Eq, Ord, Show) --- | Lamport clock simpulation. Key is 'Pid'.--- Non-present value is equivalent to 0.-newtype LamportClockSim a = LamportClockSim (State (Map Pid LocalTime) a)-    deriving (Applicative, Functor, Monad)---- | ProcessSim inside Lamport clock simpulation.-newtype ProcessSim a = ProcessSim (ReaderT Pid LamportClockSim a)-    deriving (Applicative, Functor, Monad)- class Monad m => Process m where     getPid :: m Pid -runLamportClockSim :: LamportClockSim a -> a-runLamportClockSim (LamportClockSim action) = evalState action mempty--runProcessSim :: Pid -> ProcessSim a -> LamportClockSim a-runProcessSim pid (ProcessSim action) = runReaderT action pid--preIncrementAt :: Pid -> LamportClockSim LocalTime-preIncrementAt pid =-    LamportClockSim . state $ \m -> let-        lt' = succ . fromMaybe 0 $ Map.lookup pid m-        in (lt', Map.insert pid lt' m)- getRealLocalTime :: IO LocalTime getRealLocalTime = round . (* 10000000) <$> getPOSIXTime @@ -90,19 +66,6 @@ class Process m => Clock m where     getTime :: m LamportTime     advance :: LocalTime -> m ()--instance Process ProcessSim where-    getPid = ProcessSim ask--instance Clock ProcessSim where-    getTime = ProcessSim $ do-        pid <- ask-        time <- lift $ preIncrementAt pid-        pure $ LamportTime time pid--    advance time = ProcessSim $ do-        pid <- ask-        lift . LamportClockSim . modify $ Map.insertWith max pid time  -- TODO(cblp, 2018-01-06) benchmark and compare with 'atomicModifyIORef' newtype LamportClock a = LamportClock (ReaderT (TVar LocalTime) IO a)
+ lib/CRDT/LamportClock/Simulation.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}++module CRDT.LamportClock.Simulation+    (+    -- * Lamport clock 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)++-- | Lamport clock simulation. Key is 'Pid'.+-- Non-present value is equivalent to (0, initial).+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+    }++-- | ProcessSim inside Lamport clock simulation.+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)
+ lib/Data/MultiMap.hs view
@@ -0,0 +1,55 @@+module Data.MultiMap+    ( MultiMap (..)+    , assocs+    , delete+    , deleteMany+    , empty+    , insert+    , keysSet+    , lookup+    , singleton+    ) where++import           Prelude hiding (lookup)++import           Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import           Data.Maybe (fromMaybe)+import           Data.Semigroup ((<>))+import           Data.Set (Set)+import qualified Data.Set as Set++newtype MultiMap k v = MultiMap (Map k (Set v))+    deriving (Eq, Show)++assocs :: MultiMap k v -> [(k, [v])]+assocs (MultiMap m) = Map.assocs $ Set.toList <$> m++delete :: (Ord k, Ord v) => k -> v -> MultiMap k v -> MultiMap k v+delete k v (MultiMap m) = MultiMap $ Map.update delete' k m+  where+    delete' s = let s' = Set.delete v s in if null s' then Nothing else Just s'++deleteMany ::+    (Ord k, Ord v) => k -> Set v -> MultiMap k v -> MultiMap k v+deleteMany k vs (MultiMap m) = MultiMap $ Map.update deleteMany' k m+  where+    deleteMany' s =+        let s' = Set.difference s vs in if null s' then Nothing else Just s'++empty :: MultiMap k v+empty = MultiMap Map.empty++insert :: (Ord k, Ord v) => k -> v -> MultiMap k v -> MultiMap k v+insert k v (MultiMap m) =+    MultiMap $ Map.insertWith (<>) k (Set.singleton v) m++keysSet :: MultiMap k v -> Set k+keysSet (MultiMap m) = Map.keysSet m++-- | If no key in the map then the result is empty.+lookup :: Ord k => k -> MultiMap k v -> Set v+lookup k (MultiMap m) = fromMaybe Set.empty $ Map.lookup k m++singleton :: k -> v -> MultiMap k v+singleton k v = MultiMap $ Map.singleton k $ Set.singleton v
test/ArbitraryOrphans.hs view
@@ -2,27 +2,34 @@  {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE StandaloneDeriving #-}  module ArbitraryOrphans () where  import           Test.QuickCheck (Arbitrary (..), arbitraryBoundedEnum,-                                  elements)+                                  elements, frequency, oneof) import           Test.QuickCheck.Gen (Gen (MkGen)) import           Test.QuickCheck.Instances () import           Test.QuickCheck.Random (mkQCGen)  import           CRDT.Cm.Counter (Counter (..)) import           CRDT.Cm.GSet (GSet (..))-import qualified CRDT.Cm.TwoPSet as Cm+import qualified CRDT.Cm.ORSet as CmORSet+import qualified CRDT.Cm.RGA as CmRGA+import qualified CRDT.Cm.TwoPSet as CmTwoPSet import           CRDT.Cv.GCounter (GCounter (..)) import           CRDT.Cv.LwwElementSet (LwwElementSet (..))-import           CRDT.Cv.ORSet (ORSet (..))+import qualified CRDT.Cv.ORSet as CvORSet import           CRDT.Cv.PNCounter (PNCounter (..))-import qualified CRDT.Cv.TwoPSet as Cv+import qualified CRDT.Cv.RGA as CvRGA+import qualified CRDT.Cv.TwoPSet as CvTwoPSet import           CRDT.LamportClock (LamportTime (..), Pid (..)) import           CRDT.LWW (LWW (..))+import           Data.MultiMap (MultiMap (..)) +import           Util (pattern (:-))+ instance Arbitrary (Counter a) where     arbitrary = arbitraryBoundedEnum @@ -36,19 +43,54 @@  deriving instance Arbitrary a => Arbitrary (GSet a) -instance Arbitrary a => Arbitrary (Cm.TwoPSet a) where-    arbitrary = elements [Cm.Add, Cm.Remove] <*> arbitrary+deriving instance+        (Arbitrary k, Ord k, Arbitrary v, Ord v) => Arbitrary (MultiMap k v) +instance Arbitrary a => Arbitrary (CmORSet.Intent a) where+    arbitrary = elements [CmORSet.Add, CmORSet.Remove] <*> arbitrary++instance Arbitrary a => Arbitrary (CmORSet.ORSet a) where+    arbitrary = oneof+        [ CmORSet.OpAdd    <$> arbitrary <*> arbitrary+        , CmORSet.OpRemove <$> arbitrary <*> arbitrary+        ]++instance (Arbitrary a, Ord a) => Arbitrary (CmORSet.Payload a) where+    arbitrary = CmORSet.Payload <$> arbitrary <*> arbitrary++instance Arbitrary CmORSet.Tag where+    arbitrary = CmORSet.Tag <$> arbitrary <*> arbitrary++instance Arbitrary a => Arbitrary (CmRGA.RGA a) where+    arbitrary = oneof+        [ CmRGA.OpAddAfter <$> arbitrary <*> arbitrary <*> arbitrary+        , CmRGA.OpRemove   <$> arbitrary+        ]++instance Arbitrary a => Arbitrary (CmRGA.RgaIntent a) where+    arbitrary = frequency+        [ 10 :- CmRGA.AddAfter <$> arbitrary <*> arbitrary+        ,  1 :- CmRGA.Remove   <$> arbitrary+        ]++instance (Arbitrary a, Ord a) => Arbitrary (CmRGA.RgaPayload a) where+    arbitrary = CmRGA.load <$> arbitrary++instance Arbitrary a => Arbitrary (CmTwoPSet.TwoPSet a) where+    arbitrary = elements [CmTwoPSet.Add, CmTwoPSet.Remove] <*> arbitrary+ deriving instance Arbitrary a => Arbitrary (GCounter a) -deriving instance (Arbitrary a, Ord a) => Arbitrary (ORSet a)+deriving instance (Arbitrary a, Ord a) => Arbitrary (CvORSet.ORSet a)  deriving instance (Arbitrary a, Ord a) => Arbitrary (LwwElementSet a)  instance Arbitrary a => Arbitrary (PNCounter a) where     arbitrary = PNCounter <$> arbitrary <*> arbitrary -deriving instance (Ord a, Arbitrary a) => Arbitrary (Cv.TwoPSet a)+deriving instance Arbitrary a => Arbitrary (CvRGA.RGA a)++deriving instance (Ord a, Arbitrary a) => Arbitrary (CvTwoPSet.TwoPSet a)  instance Arbitrary LamportTime where     arbitrary = LamportTime <$> arbitrary <*> arbitrary
+ test/Cm/ORSet.hs view
@@ -0,0 +1,44 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-}++{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++module Cm.ORSet where++import qualified Data.MultiMap as MultiMap+import           Test.QuickCheck (counterexample, (.&&.), (===), (==>))++import           CRDT.Cm (initial, makeAndApplyOp, query)+import           CRDT.Cm.ORSet (Intent (Add, Remove), ORSet, Tag (Tag),+                                elements)+import           CRDT.LamportClock.Simulation (runLamportClockSim,+                                               runProcessSim)++import           Laws (cmrdtLaw)+import           Util (pattern (:-), expectRight)++prop_Cm = cmrdtLaw @(ORSet Char)++-- | Example from fig. 14 from "A comprehensive study of CRDTs"+prop_fig14 α β a = expectRight . runLamportClockSim (initial @(ORSet Char)) $ do+    op1 <- runProcessSim β $ makeAndApplyOp (Add (a :: Char))+    op2 <- runProcessSim α $ makeAndApplyOp (Add a)+    op3 <- runProcessSim α $ makeAndApplyOp (Remove a)+    pure $+        α < β ==>+        check "2"   [op2]           [a :- [Tag α 0]]          .&&.+        check "23"  [op2, op3]      []                        .&&.+        check "231" [op2, op3, op1] [a :- [Tag β 0]]          .&&.+        check "1"   [op1]           [a :- [Tag β 0]]          .&&.+        check "12"  [op1, op2]      [a :- [Tag α 0, Tag β 0]] .&&.+        check "123" [op1, op2, op3] [a :- [Tag β 0]]+  where+    check opsLabel ops result =+        counterexample ("ops = " ++ opsLabel) $+        counterexample ("ops = " ++ show ops) $+        query' ops === result++query' :: (Ord a, Foldable f) => f (ORSet a) -> [(a, [Tag])]+query' = MultiMap.assocs . elements . query
test/Cm/TwoPSet.hs view
@@ -4,10 +4,16 @@  module Cm.TwoPSet where -import           CRDT.Cm.TwoPSet (TwoPSet)+import           Test.QuickCheck (Small) -import           Laws (cmrdtLaw)+import           CRDT.Cm.TwoPSet (TwoPSet (Remove)) +import           Laws (cmrdtLaw, opCommutativity)+ prop_Cm = cmrdtLaw @(TwoPSet Char) --- prop_add =+prop_remove_commutes_with_itself e = opCommutativity intentOp intentOp+  where+    intent = Remove (e :: Small Int)+    op = intent+    intentOp = (intent, op)
+ test/Cv/ORSet.hs view
@@ -0,0 +1,28 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-}++{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Cv.ORSet where++import           Prelude hiding (lookup)++import           Data.Semigroup (Semigroup ((<>)))++import           CRDT.Cv.ORSet (ORSet, add, lookup, remove)+import           CRDT.LamportClock.Simulation (runLamportClockSim,+                                               runProcessSim)++import           Laws (cvrdtLaws)+import           Util (expectRight)++test_Cv = cvrdtLaws @(ORSet Int)++prop_add pid (x :: Char) s = expectRight . runLamportClockSim undefined $+    runProcessSim pid $ not . lookup x . remove x <$> add x s++-- | Difference from 'LwwElementSet' --+-- other replica can not accidentally delete x+prop_add_merge (x :: Char) pid s1 s0 = expectRight $+    runLamportClockSim undefined $+        runProcessSim pid $ lookup x . (<> s1) <$> add x s0
+ test/Cv/RGA.hs view
@@ -0,0 +1,27 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-}++{-# LANGUAGE TypeApplications #-}++module Cv.RGA where++import           Test.QuickCheck ((===))++import           CRDT.Cv.RGA (RgaString, edit, fromString, pack, toString,+                              unpack)+import           CRDT.LamportClock.Simulation (runLamportClockSim,+                                               runProcessSim)++import           Laws (cvrdtLaws)+import           Util (expectRight)++prop_fromString_toString s pid = expectRight $ do+    s' <- runLamportClockSim undefined $ runProcessSim pid $ fromString s+    pure $ toString s' === s++test_Cv = cvrdtLaws @RgaString++prop_edit v1 s2 pid = expectRight . runLamportClockSim undefined $ do+    v2 <- runProcessSim pid $ edit s2 v1+    pure $ toString v2 === s2++prop_pack_unpack rga = unpack (pack rga) == (rga :: RgaString)
test/Cv/TwoPSet.hs view
@@ -5,23 +5,21 @@  module Cv.TwoPSet where -import           Prelude hiding (lookup)- import           Test.QuickCheck ((==>)) -import           CRDT.Cv.TwoPSet (TwoPSet (..), add, isKnown, lookup, remove)+import           CRDT.Cv.TwoPSet (TwoPSet (..), add, isKnown, member, remove)  import           Laws (cvrdtLaws)  -- | Difference from LwwElementSet and ORSet -- removal bias prop_removal_bias (s :: TwoPSet Char) x =-    not . lookup x . add x . remove x $ add x s+    not . member x . add x . remove x $ add x s -prop_add (s :: TwoPSet Char) x = not (isKnown x s) ==> lookup x (add x s)+prop_add (s :: TwoPSet Char) x = not (isKnown x s) ==> member x (add x s) -prop_remove (s :: TwoPSet Char) x = not . lookup x $ remove x s+prop_remove (s :: TwoPSet Char) x = not . member x $ remove x s  prop_add_then_remove (s :: TwoPSet Char) x =-    not . lookup x . remove x $ add x s+    not . member x . remove x $ add x s  test_Cv = cvrdtLaws @(TwoPSet Char)
test/LWW.hs view
@@ -14,23 +14,25 @@ import           Data.Semigroup ((<>)) import           Test.QuickCheck ((===)) -import           CRDT.LamportClock (runLamportClockSim, runProcessSim)-import           CRDT.LWW (LWW, assign, initial, query)+import           CRDT.LamportClock.Simulation (runLamportClockSim,+                                               runProcessSim)+import           CRDT.LWW (LWW, assign, initialize, query)  import           Laws (cmrdtLaw, cvrdtLaws)+import           Util (expectRight)  prop_Cm = cmrdtLaw @(LWW Char)  test_Cv = cvrdtLaws @(LWW Char)  prop_assign pid1 pid2 (formerValue :: Char) latterValue =-    runLamportClockSim $ do-        state1 <- runProcessSim pid1 $ initial formerValue+    expectRight . runLamportClockSim undefined $ do+        state1 <- runProcessSim pid1 $ initialize formerValue         state2 <- runProcessSim pid2 $ assign latterValue state1         pure $ query state2 === latterValue  prop_merge_with_former pid1 pid2 (formerValue :: Char) latterValue =-    runLamportClockSim $ do-        state1 <- runProcessSim pid1 $ initial formerValue+    expectRight . runLamportClockSim undefined $ do+        state1 <- runProcessSim pid1 $ initialize formerValue         state2 <- runProcessSim pid2 $ assign latterValue state1         pure $ query (state1 <> state2) === latterValue
test/Laws.hs view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -Wno-orphans #-}+ {-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}@@ -9,24 +11,28 @@ module Laws     ( cmrdtLaw     , cvrdtLaws+    , opCommutativity     ) where -import           Data.Maybe (fromMaybe, isJust)+import           Control.Monad.State.Strict (MonadState, get, modify)+import           Control.Monad.Trans (lift)+import           Data.Functor.Identity (runIdentity)+import           Data.Maybe (isJust) import           Data.Semigroup (Semigroup, (<>))-import           Test.QuickCheck (Arbitrary (..), Property, counterexample,-                                  discard, property, (.&&.), (===), (==>))+import           QuickCheck.GenT (MonadGen, liftGen, runGenT)+import qualified QuickCheck.GenT as GenT+import           Test.QuickCheck (Arbitrary (..), Property, choose,+                                  counterexample, discard, forAll, getSize,+                                  property, (.&&.), (===), (==>)) import           Test.Tasty (TestTree) import           Test.Tasty.QuickCheck (testProperty)  import           CRDT.Cm (CmRDT (..), concurrent)-import           CRDT.Cm.Counter (Counter)-import           CRDT.Cm.GSet (GSet)-import           CRDT.Cm.TwoPSet (TwoPSet) import           CRDT.Cv (CvRDT)-import           CRDT.LamportClock (Clock, ProcessSim, runLamportClockSim,-                                    runProcessSim)-import           CRDT.LWW (LWW)-import qualified CRDT.LWW as LWW+import           CRDT.LamportClock (Clock)+import           CRDT.LamportClock.Simulation (ProcessSim, ProcessSimT,+                                               runLamportClockSimT,+                                               runProcessSimT) import           Data.Semilattice (Semilattice, merge)  import           ArbitraryOrphans ()@@ -50,54 +56,83 @@ cvrdtLaws :: forall a. (Arbitrary a, CvRDT a, Eq a, Show a) => [TestTree] cvrdtLaws = semilatticeLaws @a -class Initialize op where-    type Initial op-    type Initial op = Payload op--    initialize :: Clock m => Initial op -> m (Payload op)-    default initialize-        :: (Applicative m, Initial op ~ Payload op)-        => Initial op -> m (Payload op)-    initialize = pure--instance Initialize (Counter a)--instance Initialize (GSet a)--instance Initialize (LWW a) where-    type Initial (LWW a) = a-    initialize = LWW.initial--instance Initialize (TwoPSet a)- -- | CmRDT law: concurrent ops commute cmrdtLaw     :: forall op.-    ( CmRDT op-    , Arbitrary op, Show op+    ( CmRDT op, Show op     , Arbitrary (Intent  op), Show (Intent  op)     , Arbitrary (Payload op), Show (Payload op)-    , Initialize op-    , Arbitrary (Initial op), Show (Initial op)     )     => Property cmrdtLaw = property concurrentOpsCommute   where-    concurrentOpsCommute st1 st2 seed3 in1 in2 pid1 pid2 pid3 =-        let (op1, op2, state) = runLamportClockSim $ (,,)-                <$> runProcessSim pid1 (makeOp @op in1 st1 `orElse` discard)-                <*> runProcessSim pid2 (makeOp @op in2 st2 `orElse` discard)-                <*> runProcessSim pid3 (initialize @op seed3)-        in  concurrent op1 op2 ==> opCommutativity (in1, op1) (in2, op2) state-    opCommutativity (in1, op1) (in2, op2) state =-        isJust (makeOp @op @ProcessSim in1 state) ==>-        isJust (makeOp @op @ProcessSim in2 state) ==>-            counterexample-                ( show in1 ++ " must be valid after " ++ show op2 ++-                  " applied to " ++ show state )-                (isJust $ makeOp @op @ProcessSim in1 $ apply op2 state)-            .&&.-            (apply op1 . apply op2) state === (apply op2 . apply op1) state+    concurrentOpsCommute payload pid1 pid2 pid3 =+        pid1 < pid2 && pid2 < pid3 ==>+        forAll genOps $ \case+            Right ((in1, op1), (in2, op2), state3) ->+                concurrent op1 op2 ==>+                opCommutativity (in1, op1) (in2, op2) state3+            Left _ -> discard+      where+        genOps = fmap runIdentity $ runGenT $ runLamportClockSimT payload $ (,,)+            <$> runProcessSimT pid1 (do+                    _ <- genAndApplyOps @op+                    genAndApplyOp @op)+            <*> runProcessSimT pid2 (do+                    _ <- genAndApplyOps @op+                    genAndApplyOp @op)+            <*> runProcessSimT pid3 (do+                    _ <- genAndApplyOps @op+                    get) -orElse :: Maybe a -> a -> a-orElse = flip fromMaybe+opCommutativity+    :: forall op.+    (CmRDT op, Show op, Show (Intent  op), Show (Payload op))+    => (Intent op, op) -- ^ the op must be made from the intent+    -> (Intent op, op) -- ^ the op must be made from the intent+    -> Payload op -- ^ any reachable state+    -> Property+opCommutativity (in1, op1) (in2, op2) state =+    isJust (makeOp' in1 state) ==>+    isJust (makeOp' in2 state) ==>+        counterexample+            ( show in2 ++ " must be valid after " ++ show op1 +++              " applied to " ++ show state )+            (isJust $ makeOp' in2 $ apply op1 state)+        .&&.+        (apply op1 . apply op2) state === (apply op2 . apply op1) state+  where+    makeOp' = makeOp @op @(ProcessSim (Payload op))++genAndApplyOp+    :: ( CmRDT op, Arbitrary (Intent op)+       , Clock m, MonadState (Payload op) m, MonadGen m+       , Show op, Show (Intent op), Show (Payload op)+       )+    => m (Intent op, op)+genAndApplyOp = do+    payload <- get+    intent <- liftGen arbitrary+    case makeOp intent payload of+        Nothing -> genAndApplyOp+        Just opAction -> do+            op <- opAction+            modify $ apply op+            pure (intent, op)++genAndApplyOps+    :: ( CmRDT op, Arbitrary (Intent op)+       , Clock m, MonadState (Payload op) m, MonadGen m+       , Show op, Show (Intent op), Show (Payload op)+       )+    => m [(Intent op, op)]+genAndApplyOps = GenT.listOf genAndApplyOp++instance MonadGen m => MonadGen (ProcessSimT s m) where+    liftGen = lift . liftGen+    variant = undefined+    sized f = do+        size <- liftGen getSize+        f size+    resize = undefined+    choose = liftGen . choose
test/LwwElementSet.hs view
@@ -16,26 +16,28 @@ import           Data.Semigroup ((<>))  import           CRDT.Cv.LwwElementSet (LwwElementSet, add, lookup, remove)-import           CRDT.LamportClock (runLamportClockSim, runProcessSim)+import           CRDT.LamportClock.Simulation (runLamportClockSim,+                                               runProcessSim)  import           Laws (cvrdtLaws)+import           Util (expectRight)  test_Cv = cvrdtLaws @(LwwElementSet Char)  prop_add (s :: LwwElementSet Char) x pid1 =-    runLamportClockSim $ do+    expectRight . runLamportClockSim undefined $ do         s1 <- runProcessSim pid1 $ add x s         pure $ lookup x s1  prop_remove (s :: LwwElementSet Char) x pid1 pid2 =-    runLamportClockSim $ do+    expectRight . runLamportClockSim undefined $ do         s1 <- runProcessSim pid1 $ add x s         s2 <- runProcessSim pid2 $ remove x s1         pure . not $ lookup x s2  -- | Difference from 'TwoPSet' -- no removal bias prop_no_removal_bias (s :: LwwElementSet Char) x pid1 pid2 pid3 =-    runLamportClockSim $ do+    expectRight . runLamportClockSim undefined $ do         s1 <- runProcessSim pid1 $ add x s         s2 <- runProcessSim pid2 $ remove x s1         s3 <- runProcessSim pid3 $ add x s2@@ -43,7 +45,7 @@  -- | Difference from 'ORSet' -- other replica can accidentally delete x prop_they_accidentally_delete_our_value (s :: LwwElementSet Char) x pid1 pid2 =-    runLamportClockSim $ do+    expectRight . runLamportClockSim undefined $ do         s1 <- runProcessSim pid1 $ add x s         s2 <- runProcessSim pid2 $ remove x =<< add x s         pure . not . lookup x $ s1 <> s2
− test/ORSet.hs
@@ -1,25 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-signatures #-}--{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}--module ORSet where--import           Prelude hiding (lookup)--import           Data.Semigroup (Semigroup ((<>)))--import           CRDT.Cv.ORSet (ORSet, add, lookup, remove)-import           CRDT.LamportClock (runLamportClockSim, runProcessSim)--import           Laws (cvrdtLaws)--test_Cv = cvrdtLaws @(ORSet Int)--prop_add pid (x :: Char) s =-    runLamportClockSim $ runProcessSim pid $ not . lookup x . remove x <$> add x s---- | Difference from 'LwwElementSet' ----- other replica can not accidentally delete x-prop_add_merge (x :: Char) pid s1 s0 =-    runLamportClockSim $ runProcessSim pid $ lookup x . (<> s1) <$> add x s0
+ test/RGA.hs view
@@ -0,0 +1,81 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-}++{-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE TypeApplications #-}++module RGA where++import           Control.Monad.State.Strict (get)+import           Data.Maybe (isJust)+import qualified Data.Vector as Vector+import           Test.QuickCheck (conjoin, counterexample, (===))++import           CRDT.Cm (apply, initial, makeAndApplyOp, makeOp)+import           CRDT.Cm.RGA (RGA (OpAddAfter), RgaIntent (AddAfter),+                              fromString, load, toString)+import           CRDT.LamportClock (LamportTime (LamportTime), Pid (Pid),+                                    advance)+import           CRDT.LamportClock.Simulation (ProcessSim, runLamportClockSim,+                                               runProcessSim)++import           Laws (cmrdtLaw)+import           Util (pattern (:-), expectRightK)++prop_makeOp =+    isJust+        (makeOp @(RGA Char) (AddAfter Nothing 'a') (initial @(RGA Char))+        :: Maybe (ProcessSim () (RGA Char)))++prop_makeAndApplyOp = conjoin+    [ counterexample "result3"  $ result3  === Right (op3, payload3)+    , counterexample "result2"  $ result2  === Right (op2, payload2)+    , counterexample "result1"  $ result1  === Right (op1, payload1)+    , counterexample "result12" $ result12 === payload12+    , counterexample "results=" $ result21 === result12+    ]+  where+    time1 = LamportTime 4 $ Pid 1+    time2 = LamportTime 4 $ Pid 2+    time3 = LamportTime 3 $ Pid 3+    op1 = OpAddAfter Nothing '1' time1+    op2 = OpAddAfter Nothing '2' time2+    op3 = OpAddAfter Nothing '3' time3+    payload3 = load $ Vector.singleton (time3 :- Just '3')+    payload1 = load $ Vector.fromList [time1 :- Just '1', time3 :- Just '3']+    payload2 = load $ Vector.fromList [time2 :- Just '2', time3 :- Just '3']+    payload12 = load $ Vector.fromList+        [time2 :- Just '2', time1 :- Just '1', time3 :- Just '3']+    result3 = runLamportClockSim (initial @(RGA Char)) $+        runProcessSim (Pid 3) $ do+            advance 2+            (,) <$> makeAndApplyOp @(RGA Char) (AddAfter Nothing '3')+                <*> get+    result2 = runLamportClockSim payload3 $ runProcessSim (Pid 2) $ do+        advance 1+        (,) <$> makeAndApplyOp @(RGA Char) (AddAfter Nothing '2')+            <*> get+    result1 = runLamportClockSim payload3 $ runProcessSim (Pid 1) $+        (,) <$> makeAndApplyOp @(RGA Char) (AddAfter Nothing '1')+            <*> get+    result12 = apply op2 payload1+    result21 = apply op1 payload2++prop_fromString s pid =+    result ===+    Right (load $ Vector.fromList+        [LamportTime t pid :- Just c | t <- [1..] | c <- s])+  where+    result = runLamportClockSim (initial @(RGA Char)) $+        runProcessSim pid $ do+            _ <- fromString s+            get++prop_fromString_toString s pid =+    expectRightK result $ \s' -> toString s' === s+  where+    result = runLamportClockSim (initial @(RGA Char)) $ runProcessSim pid $ do+        _ <- fromString s+        get++prop_Cm = cmrdtLaw @(RGA Char)
+ test/Util.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE PatternSynonyms #-}++module Util where++import           Test.QuickCheck (Property, Testable, counterexample, property)++expectRight :: Testable a => Either String a -> Property+expectRight e = expectRightK e id++expectRightK :: Testable b => Either String a -> (a -> b) -> Property+expectRightK e f = case e of+    Left l  -> counterexample l $ property False+    Right a -> property $ f a++pattern (:-) :: a -> b -> (a, b)+pattern a :- b = (a, b)+infix 0 :-