packages feed

crdt 0.5 → 1.0

raw patch · 36 files changed

+753/−396 lines, 36 filesdep +enummapsetdep +latticesdep +microlensPVP ok

version bump matches the API change (PVP)

Dependencies added: enummapset, lattices, microlens, microlens-ghc, microlens-mtl, mtl

API changes (from Hackage documentation)

- CRDT.Cm: type family State op :: Type;
- CRDT.Cm: update :: CmRDT op => op -> State op -> State op
- CRDT.Cm: }
- CRDT.GCounter.Cm: Increment :: GCounter a
- CRDT.GCounter.Cm: data GCounter a
- CRDT.GCounter.Cm: initial :: Num a => a
- CRDT.GCounter.Cm: instance GHC.Num.Num a => CRDT.Cm.CmRDT (CRDT.GCounter.Cm.GCounter a)
- CRDT.GCounter.Cm: instance GHC.Show.Show (CRDT.GCounter.Cm.GCounter a)
- CRDT.GCounter.Cv: data GCounter a
- CRDT.GCounter.Cv: increment :: Num a => Word -> GCounter a -> GCounter a
- CRDT.GCounter.Cv: initial :: GCounter a
- CRDT.GCounter.Cv: query :: Num a => GCounter a -> a
- CRDT.GCounter.Cv.Internal: GCounter :: (IntMap a) -> GCounter a
- CRDT.GCounter.Cv.Internal: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.GCounter.Cv.Internal.GCounter a)
- CRDT.GCounter.Cv.Internal: instance GHC.Classes.Ord a => Data.Semigroup.Semigroup (CRDT.GCounter.Cv.Internal.GCounter a)
- CRDT.GCounter.Cv.Internal: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (CRDT.GCounter.Cv.Internal.GCounter a)
- CRDT.GCounter.Cv.Internal: instance GHC.Show.Show a => GHC.Show.Show (CRDT.GCounter.Cv.Internal.GCounter a)
- CRDT.GCounter.Cv.Internal: newtype GCounter a
- CRDT.GSet.Cv: add :: Ord a => a -> GSet a -> GSet a
- CRDT.GSet.Cv: initial :: GSet a
- CRDT.GSet.Cv: query :: Ord a => a -> GSet a -> Bool
- CRDT.GSet.Cv: type GSet = Set
- CRDT.GSet.Cv.Internal: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (Data.Set.Base.Set a)
- CRDT.GSet.Cv.Internal: type GSet = Set
- CRDT.LWW: Write :: !Timestamp -> !a -> LWW a
- CRDT.LWW: [timestamp] :: LWW a -> !Timestamp
- CRDT.LWW: [value] :: LWW a -> !a
- CRDT.LWW: data LWW a
- CRDT.LWW: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.LWW.LWW a)
- CRDT.LWW: instance GHC.Classes.Ord a => CRDT.Cm.CmRDT (CRDT.LWW.LWW a)
- CRDT.LWW: instance GHC.Classes.Ord a => Data.Semigroup.Semigroup (CRDT.LWW.LWW a)
- CRDT.LWW: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (CRDT.LWW.LWW a)
- CRDT.LWW: instance GHC.Classes.Ord a => GHC.Classes.Ord (CRDT.LWW.LWW a)
- CRDT.LWW: instance GHC.Show.Show a => GHC.Show.Show (CRDT.LWW.LWW a)
- CRDT.LWW: point :: Timestamp -> a -> LWW a
- CRDT.LWW: query :: LWW a -> a
- CRDT.LWW: write :: Ord a => Timestamp -> a -> LWW a -> LWW a
- CRDT.PNCounter.Cm: Decrement :: PNCounter a
- CRDT.PNCounter.Cm: Increment :: PNCounter a
- CRDT.PNCounter.Cm: data PNCounter a
- CRDT.PNCounter.Cm: initial :: Num a => a
- CRDT.PNCounter.Cm: instance GHC.Enum.Bounded (CRDT.PNCounter.Cm.PNCounter a)
- CRDT.PNCounter.Cm: instance GHC.Enum.Enum (CRDT.PNCounter.Cm.PNCounter a)
- CRDT.PNCounter.Cm: instance GHC.Num.Num a => CRDT.Cm.CmRDT (CRDT.PNCounter.Cm.PNCounter a)
- CRDT.PNCounter.Cm: instance GHC.Show.Show (CRDT.PNCounter.Cm.PNCounter a)
- CRDT.PNCounter.Cv: data PNCounter a
- CRDT.PNCounter.Cv: decrement :: Num a => Word -> PNCounter a -> PNCounter a
- CRDT.PNCounter.Cv: increment :: Num a => Word -> PNCounter a -> PNCounter a
- CRDT.PNCounter.Cv: initial :: PNCounter a
- CRDT.PNCounter.Cv: query :: Num a => PNCounter a -> a
- CRDT.PNCounter.Cv.Internal: PNCounter :: !(GCounter a) -> !(GCounter a) -> PNCounter a
- CRDT.PNCounter.Cv.Internal: [negative] :: PNCounter a -> !(GCounter a)
- CRDT.PNCounter.Cv.Internal: [positive] :: PNCounter a -> !(GCounter a)
- CRDT.PNCounter.Cv.Internal: data PNCounter a
- CRDT.PNCounter.Cv.Internal: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.PNCounter.Cv.Internal.PNCounter a)
- CRDT.PNCounter.Cv.Internal: instance GHC.Classes.Ord a => Data.Semigroup.Semigroup (CRDT.PNCounter.Cv.Internal.PNCounter a)
- CRDT.PNCounter.Cv.Internal: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (CRDT.PNCounter.Cv.Internal.PNCounter a)
- CRDT.PNCounter.Cv.Internal: instance GHC.Show.Show a => GHC.Show.Show (CRDT.PNCounter.Cv.Internal.PNCounter a)
- CRDT.Timestamp: type Timestamp = Natural
- Data.Semilattice: slappend :: Semilattice a => a -> a -> a
+ CRDT.Cm: concurrent :: PartialOrd a => a -> a -> Bool
+ CRDT.Cm: updateAtSource :: (CmRDT payload op update, Clock m) => op -> m update
+ CRDT.Cm: updateAtSourcePre :: CmRDT payload op update => op -> payload -> Bool
+ CRDT.Cm: updateDownstream :: CmRDT payload op update => update -> payload -> payload
+ CRDT.Cm.Counter: Counter :: a -> Counter a
+ CRDT.Cm.Counter: Decrement :: CounterOp a
+ CRDT.Cm.Counter: Increment :: CounterOp a
+ CRDT.Cm.Counter: data CounterOp a
+ CRDT.Cm.Counter: initial :: Num a => Counter a
+ CRDT.Cm.Counter: instance (GHC.Num.Num a, GHC.Classes.Eq a) => CRDT.Cm.CmRDT (CRDT.Cm.Counter.Counter a) (CRDT.Cm.Counter.CounterOp a) (CRDT.Cm.Counter.CounterOp a)
+ CRDT.Cm.Counter: instance Algebra.PartialOrd.PartialOrd (CRDT.Cm.Counter.CounterOp a)
+ CRDT.Cm.Counter: instance Data.Observe.Observe (CRDT.Cm.Counter.Counter a)
+ CRDT.Cm.Counter: instance GHC.Classes.Eq (CRDT.Cm.Counter.CounterOp a)
+ CRDT.Cm.Counter: instance GHC.Enum.Bounded (CRDT.Cm.Counter.CounterOp a)
+ CRDT.Cm.Counter: instance GHC.Enum.Enum (CRDT.Cm.Counter.CounterOp a)
+ CRDT.Cm.Counter: instance GHC.Show.Show (CRDT.Cm.Counter.CounterOp a)
+ CRDT.Cm.Counter: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.Counter.Counter a)
+ CRDT.Cm.Counter: newtype Counter a
+ CRDT.Cm.GSet: Add :: a -> Add a
+ CRDT.Cm.GSet: initial :: GSet a
+ CRDT.Cm.GSet: instance Data.Observe.Observe (CRDT.Cv.GSet.GSet a)
+ CRDT.Cm.GSet: instance GHC.Classes.Eq a => Algebra.PartialOrd.PartialOrd (CRDT.Cm.GSet.Add a)
+ CRDT.Cm.GSet: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.GSet.Add a)
+ CRDT.Cm.GSet: instance GHC.Classes.Ord a => CRDT.Cm.CmRDT (Data.Set.Base.Set a) (CRDT.Cm.GSet.Add a) (CRDT.Cm.GSet.Add a)
+ CRDT.Cm.GSet: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.GSet.Add a)
+ CRDT.Cm.GSet: lookup :: Ord a => a -> GSet a -> Bool
+ CRDT.Cm.GSet: newtype Add a
+ CRDT.Cm.GSet: type GSet = Set
+ CRDT.Cm.LWW: Assign :: a -> Assign a
+ CRDT.Cm.LWW: LWW :: !Timestamp -> !a -> LWW a
+ CRDT.Cm.LWW: [timestamp] :: LWW a -> !Timestamp
+ CRDT.Cm.LWW: [value] :: LWW a -> !a
+ CRDT.Cm.LWW: data LWW a
+ CRDT.Cm.LWW: instance Algebra.PartialOrd.PartialOrd (CRDT.Cv.LWW.LWW a)
+ CRDT.Cm.LWW: instance Data.Observe.Observe (CRDT.Cv.LWW.LWW a)
+ CRDT.Cm.LWW: instance GHC.Classes.Eq a => CRDT.Cm.CmRDT (CRDT.Cv.LWW.LWW a) (CRDT.Cm.LWW.Assign a) (CRDT.Cv.LWW.LWW a)
+ CRDT.Cm.LWW: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.LWW.Assign a)
+ CRDT.Cm.LWW: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.LWW.Assign a)
+ CRDT.Cm.LWW: newtype Assign a
+ CRDT.Cm.TPSet: Add :: a -> TPSetOp a
+ CRDT.Cm.TPSet: Remove :: a -> TPSetOp a
+ CRDT.Cm.TPSet: TPSet :: Set a -> TPSet a
+ CRDT.Cm.TPSet: [payload] :: TPSet a -> Set a
+ CRDT.Cm.TPSet: data TPSetOp a
+ CRDT.Cm.TPSet: initial :: TPSet a
+ CRDT.Cm.TPSet: instance Data.Observe.Observe (CRDT.Cm.TPSet.TPSet a)
+ CRDT.Cm.TPSet: instance GHC.Classes.Eq a => Algebra.PartialOrd.PartialOrd (CRDT.Cm.TPSet.TPSetOp a)
+ CRDT.Cm.TPSet: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.TPSet.TPSetOp a)
+ CRDT.Cm.TPSet: instance GHC.Classes.Ord a => CRDT.Cm.CmRDT (CRDT.Cm.TPSet.TPSet a) (CRDT.Cm.TPSet.TPSetOp a) (CRDT.Cm.TPSet.TPSetOp a)
+ CRDT.Cm.TPSet: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.TPSet.TPSet a)
+ CRDT.Cm.TPSet: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.TPSet.TPSetOp a)
+ CRDT.Cm.TPSet: lookup :: Ord a => a -> TPSet a -> Bool
+ CRDT.Cm.TPSet: newtype TPSet a
+ CRDT.Cm.TPSet: updateAtSource :: (CmRDT payload op update, Clock m) => op -> m update
+ CRDT.Cm.TPSet: updateDownstream :: CmRDT payload op update => update -> payload -> payload
+ CRDT.Cv.GCounter: GCounter :: (IntMap a) -> GCounter a
+ CRDT.Cv.GCounter: increment :: Num a => Word -> GCounter a -> GCounter a
+ CRDT.Cv.GCounter: initial :: GCounter a
+ CRDT.Cv.GCounter: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cv.GCounter.GCounter a)
+ CRDT.Cv.GCounter: instance GHC.Classes.Ord a => Data.Semigroup.Semigroup (CRDT.Cv.GCounter.GCounter a)
+ CRDT.Cv.GCounter: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (CRDT.Cv.GCounter.GCounter a)
+ CRDT.Cv.GCounter: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cv.GCounter.GCounter a)
+ CRDT.Cv.GCounter: newtype GCounter a
+ CRDT.Cv.GCounter: query :: Num a => GCounter a -> a
+ CRDT.Cv.GSet: add :: Ord a => a -> GSet a -> GSet a
+ CRDT.Cv.GSet: initial :: GSet a
+ CRDT.Cv.GSet: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (Data.Set.Base.Set a)
+ CRDT.Cv.GSet: query :: Ord a => a -> GSet a -> Bool
+ CRDT.Cv.GSet: type GSet = Set
+ CRDT.Cv.LWW: LWW :: !Timestamp -> !a -> LWW a
+ CRDT.Cv.LWW: [timestamp] :: LWW a -> !Timestamp
+ CRDT.Cv.LWW: [value] :: LWW a -> !a
+ CRDT.Cv.LWW: assign :: Clock f => a -> LWW a -> f (LWW a)
+ CRDT.Cv.LWW: data LWW a
+ CRDT.Cv.LWW: initial :: Clock f => a -> f (LWW a)
+ CRDT.Cv.LWW: instance Data.Semigroup.Semigroup (CRDT.Cv.LWW.LWW a)
+ CRDT.Cv.LWW: instance Data.Semilattice.Semilattice (CRDT.Cv.LWW.LWW a)
+ CRDT.Cv.LWW: instance GHC.Classes.Eq (CRDT.Cv.LWW.LWW a)
+ CRDT.Cv.LWW: instance GHC.Classes.Ord (CRDT.Cv.LWW.LWW a)
+ CRDT.Cv.LWW: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cv.LWW.LWW a)
+ CRDT.Cv.LWW: query :: LWW a -> a
+ CRDT.Cv.Max: Max :: a -> Max a
+ CRDT.Cv.Max: [getMax] :: Max a -> a
+ CRDT.Cv.Max: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (Data.Semigroup.Max a)
+ CRDT.Cv.Max: newtype Max a :: * -> *
+ CRDT.Cv.Max: point :: a -> Max a
+ CRDT.Cv.Max: query :: Max a -> a
+ CRDT.Cv.PNCounter: PNCounter :: !(GCounter a) -> !(GCounter a) -> PNCounter a
+ CRDT.Cv.PNCounter: [negative] :: PNCounter a -> !(GCounter a)
+ CRDT.Cv.PNCounter: [positive] :: PNCounter a -> !(GCounter a)
+ CRDT.Cv.PNCounter: data PNCounter a
+ CRDT.Cv.PNCounter: decrement :: Num a => Word -> PNCounter a -> PNCounter a
+ CRDT.Cv.PNCounter: increment :: Num a => Word -> PNCounter a -> PNCounter a
+ CRDT.Cv.PNCounter: initial :: PNCounter a
+ CRDT.Cv.PNCounter: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cv.PNCounter.PNCounter a)
+ CRDT.Cv.PNCounter: instance GHC.Classes.Ord a => Data.Semigroup.Semigroup (CRDT.Cv.PNCounter.PNCounter a)
+ CRDT.Cv.PNCounter: instance GHC.Classes.Ord a => Data.Semilattice.Semilattice (CRDT.Cv.PNCounter.PNCounter a)
+ CRDT.Cv.PNCounter: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cv.PNCounter.PNCounter a)
+ CRDT.Cv.PNCounter: query :: Num a => PNCounter a -> a
+ Data.Observe: class Observe a where type Observed a :: Type where {
+ Data.Observe: observe :: Observe a => a -> Observed a
+ Data.Observe: type family Observed a :: Type;
+ Data.Observe: }
+ Data.Semilattice: merge :: Semilattice a => a -> a -> a
+ LamportClock: Pid :: Word32 -> Pid
+ LamportClock: Timestamp :: !Time -> !Pid -> Timestamp
+ LamportClock: barrier :: [Pid] -> LamportClock ()
+ LamportClock: class Applicative f => Clock f
+ LamportClock: data Timestamp
+ LamportClock: instance GHC.Classes.Eq LamportClock.Pid
+ LamportClock: instance GHC.Classes.Eq LamportClock.Timestamp
+ LamportClock: instance GHC.Classes.Ord LamportClock.Pid
+ LamportClock: instance GHC.Classes.Ord LamportClock.Timestamp
+ LamportClock: instance GHC.Enum.Enum LamportClock.Pid
+ LamportClock: instance GHC.Show.Show LamportClock.Pid
+ LamportClock: instance GHC.Show.Show LamportClock.Timestamp
+ LamportClock: instance LamportClock.Clock LamportClock.Process
+ LamportClock: newTimestamp :: Clock f => f Timestamp
+ LamportClock: newtype Pid
+ LamportClock: runLamportClock :: LamportClock a -> a
+ LamportClock: runProcess :: Pid -> Process a -> LamportClock a
+ LamportClock: type LamportClock = State (EnumMap Pid Time)
+ LamportClock: type Process = ReaderT Pid LamportClock
+ LamportClock: type Time = Word
- CRDT.Cm: class CmRDT op where type State op :: Type where {
+ CRDT.Cm: class (Observe payload, Eq (Observed payload), PartialOrd update) => CmRDT payload op update | payload -> op, op -> update, update -> payload where updateAtSourcePre _ _ = True
- Data.Semilattice: infixr 6 `slappend`
+ Data.Semilattice: infixr 6 `merge`

Files

crdt.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           crdt-version:        0.5+version:        1.0 synopsis:       Conflict-free replicated data types description:    Definitions of CmRDT and CvRDT. Implementations for some classic CRDTs. category:       Distributed Systems@@ -26,20 +26,29 @@   build-depends:       base >= 4.9 && < 4.10     , containers+    , enummapset+    , lattices+    , microlens+    , microlens-ghc+    , microlens-mtl+    , mtl   exposed-modules:       CRDT.Cm+      CRDT.Cm.Counter+      CRDT.Cm.GSet+      CRDT.Cm.LWW+      CRDT.Cm.TPSet       CRDT.Cv-      CRDT.GCounter.Cm-      CRDT.GCounter.Cv-      CRDT.GCounter.Cv.Internal-      CRDT.GSet.Cv-      CRDT.GSet.Cv.Internal-      CRDT.LWW-      CRDT.PNCounter.Cm-      CRDT.PNCounter.Cv-      CRDT.PNCounter.Cv.Internal-      CRDT.Timestamp+      CRDT.Cv.GCounter+      CRDT.Cv.GSet+      CRDT.Cv.LWW+      CRDT.Cv.Max+      CRDT.Cv.PNCounter+      Data.Observe       Data.Semilattice+      LamportClock+  other-modules:+      Lens.Micro.Extra   default-language: Haskell2010  test-suite test@@ -55,9 +64,13 @@     , tasty-quickcheck     , crdt   other-modules:+      ArbitraryOrphans+      Counter       GCounter       GSet       Laws       LWW+      Max       PNCounter+      TPSet   default-language: Haskell2010
lib/CRDT/Cm.hs view
@@ -1,24 +1,76 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TypeFamilies #-}  module CRDT.Cm     ( CmRDT (..)+    , concurrent     ) where -import           Data.Kind (Type)+import           Algebra.PartialOrd (PartialOrd (leq))+import           Data.Observe (Observe (..)) +import           LamportClock (Clock)++-- | TODO(cblp, 2017-09-29) import from lattices >= 1.6+comparable :: PartialOrd a => a -> a -> Bool+comparable a b = a `leq` b || b `leq` a++-- | Not comparable, i. e. ¬(a ≤ b) ∧ ¬(b ≤ a).+concurrent :: PartialOrd a => a -> a -> Bool+concurrent a b = not $ comparable a b+ {- | Operation-based, or commutative (Cm) replicated data type. -[Commutativity law]+== Implementation -    @'update' op1 . 'update' op2 == 'update' op2 . 'update' op1@+In Haskell, a CmRDT implementation consists of 3 types —+a __payload__, an __operation__ (@op@) and an __update__. +[Payload]+    Internal state of a replica.+[Operation]+    User's request to update.+[Update]+    Operation to be applied to other replicas.++For many types /operation/ and /update/ may be the same.+But for 'CRDT.Cm.LWW.LWW', for instance, this rule doesn't hold:+user can request only value, and type attaches a timestamp to it.++== Additional constraint — commutativity law++Concurrent updates are observed equally.++@+∀ up1 up2 s .+'concurrent' up1 up2 ==>+    'observe' ('updateDownstream' up1 . 'updateDownstream' up2 $ s) ==+    'observe' ('updateDownstream' up2 . 'updateDownstream' up1 $ s)+@+ Idempotency doesn't need to hold. -}-class CmRDT op where -    -- | The type of the target value-    type State op :: Type+class (Observe payload, Eq (Observed payload), PartialOrd update)+        => CmRDT payload op update+        | payload -> op, op -> update, update -> payload+        where -    -- | Apply operation to a value-    update :: op -> State op -> State op+    -- | Precondition for 'updateAtSource'.+    -- Calculates if the operation is applicable to the current state.+    updateAtSourcePre :: op -> payload -> Bool+    updateAtSourcePre _ _ = True++    -- | Generate an update to the local and remote replicas.+    -- Doesn't have sense if 'updateAtSourcePre' is false.+    --+    -- May or may not use clock.+    updateAtSource :: Clock m => op -> m update++    -- | Apply an update to the payload.+    -- An invalid update must be ignored.+    updateDownstream :: update -> payload -> payload
+ lib/CRDT/Cm/Counter.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++module CRDT.Cm.Counter+    ( Counter (..)+    , CounterOp (..)+    , initial+    ) where++import           Algebra.PartialOrd (PartialOrd (..))+import           Data.Observe (Observe (..))++import           CRDT.Cm (CmRDT (..))++newtype Counter a = Counter a+    deriving (Show)++data CounterOp a = Increment | Decrement+    deriving (Bounded, Enum, Eq, Show)++instance (Num a, Eq a) => CmRDT (Counter a) (CounterOp a) (CounterOp a) where+    updateAtSource = pure+    updateDownstream = opToFunc++instance Observe (Counter a) where+    type Observed (Counter a) = a+    observe (Counter c) = c++-- | Empty order, allowing arbitrary reordering+instance PartialOrd (CounterOp a) where+    leq _ _ = False++initial :: Num a => Counter a+initial = Counter 0++opToFunc :: Num a => CounterOp a -> Counter a -> Counter a+opToFunc op (Counter c) =+    Counter $ case op of+        Increment -> c + 1+        Decrement -> c - 1
+ lib/CRDT/Cm/GSet.hs view
@@ -0,0 +1,43 @@+{-# OPTIONS_GHC -Wno-orphans #-}++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++module CRDT.Cm.GSet+    ( GSet+    , Add (..)+    , initial+    , lookup+    ) where++import           Prelude hiding (lookup)++import           Algebra.PartialOrd (PartialOrd (leq))+import           Data.Observe (Observe (..))+import           Data.Set (Set)+import qualified Data.Set as Set++import           CRDT.Cm (CmRDT (..))+import           CRDT.Cv.GSet (GSet)++initial :: GSet a+initial = Set.empty++-- | query lookup+lookup :: Ord a => a -> GSet a -> Bool+lookup = Set.member++newtype Add a = Add a+    deriving (Eq, Show)++instance Ord a => CmRDT (Set a) (Add a) (Add a) where+    updateAtSource = pure+    updateDownstream (Add a) = Set.insert a++instance Observe (GSet a) where+    type Observed (GSet a) = Set a+    observe = id++instance Eq a => PartialOrd (Add a) where+    leq _ _ = False
+ lib/CRDT/Cm/LWW.hs view
@@ -0,0 +1,35 @@+{-# OPTIONS_GHC -Wno-orphans #-} -- TODO(cblp, 2017-10-02) join with Cv.LWW++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}++module CRDT.Cm.LWW+    ( LWW (..)+    , Assign (..)+    ) where++import           Algebra.PartialOrd (PartialOrd (..))+import           Data.Observe (Observe (..))+import           Data.Semigroup ((<>))+import           Lens.Micro ((<&>))++import           CRDT.Cm (CmRDT (..))+import           CRDT.Cv.LWW (LWW (..))+import           LamportClock (Clock (newTimestamp))++instance PartialOrd (LWW a) where+    leq _ _ = False++newtype Assign a = Assign a+    deriving (Eq, Show)++instance Eq a => CmRDT (LWW a) (Assign a) (LWW a) where+    updateAtSource (Assign value) =+        newTimestamp <&> \t -> LWW{timestamp = t, value}+    updateDownstream = (<>)++instance Observe (LWW a) where+    type Observed (LWW a) = a+    observe = value
+ lib/CRDT/Cm/TPSet.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}++-- | TODO(cblp, 2017-09-29) USet?+module CRDT.Cm.TPSet+    ( TPSet (..)+    , TPSetOp (..)+    , initial+    , lookup+    , updateAtSource+    , updateDownstream+    ) where++import           Prelude hiding (lookup)++import           Algebra.PartialOrd (PartialOrd (..))+import           Data.Observe (Observe (..))+import           Data.Set (Set)+import qualified Data.Set as Set++import           CRDT.Cm (CmRDT (..))++newtype TPSet a = TPSet{payload :: Set a}+    deriving (Show)++data TPSetOp a = Add a | Remove a+    deriving (Eq, Show)++initial :: TPSet a+initial = TPSet Set.empty++-- | query lookup+lookup :: Ord a => a -> TPSet a -> Bool+lookup a TPSet{payload} = Set.member a payload++instance Ord a => CmRDT (TPSet a) (TPSetOp a) (TPSetOp a) where+    updateAtSourcePre op payload = case op of+        Add _     -> True+        Remove a  -> lookup a payload++    updateAtSource = pure++    updateDownstream op TPSet{payload} = case op of+        Add a     -> TPSet{payload = Set.insert a payload}+        Remove a  -> TPSet{payload = Set.delete a payload}++instance Observe (TPSet a) where+    type Observed (TPSet a) = Set a+    observe = payload++instance Eq a => PartialOrd (TPSetOp a) where+    leq (Remove a) (Add b) = a == b -- `Remove e` can occur only after `Add e`+    leq _ _ = False -- Any other are not ordered
lib/CRDT/Cv.hs view
@@ -4,7 +4,7 @@     ( CvRDT     ) where -import Data.Semilattice (Semilattice)+import           Data.Semilattice (Semilattice)  {- | State-based, or convergent (Cv) replicated data type.@@ -13,6 +13,7 @@  Query function is not needed. State itself is exposed. In other words, @query = 'id'@.+Some types may offer more convenient query functions.  Actually, a CvRDT is nothing more a 'Semilattice'. -}
+ lib/CRDT/Cv/GCounter.hs view
@@ -0,0 +1,42 @@+module CRDT.Cv.GCounter+    ( GCounter (..)+    , initial+    , query+    -- * Operation+    , increment+    ) where++import           Data.IntMap.Strict (IntMap)+import qualified Data.IntMap.Strict as IntMap+import           Data.Semigroup (Semigroup ((<>)))++import           CRDT.Cv (CvRDT)++-- | Grow-only counter.+newtype GCounter a = GCounter (IntMap a)+    deriving (Eq, Show)++instance Ord a => Semigroup (GCounter a) where+    GCounter x <> GCounter y = GCounter $ IntMap.unionWith max x y++instance Ord a => CvRDT (GCounter a)++-- | Increment counter+increment+    :: Num a+    => Word -- ^ replica id+    -> GCounter a+    -> GCounter a+increment replicaId (GCounter imap) = GCounter (IntMap.insertWith (+) i 1 imap)+  where+    i = fromIntegral replicaId++-- | Initial state+initial :: GCounter a+initial = GCounter IntMap.empty++-- | Get value from the state+query :: Num a => GCounter a -> a+query (GCounter v) = mapSum v+  where+    mapSum = IntMap.foldr (+) 0
+ lib/CRDT/Cv/GSet.hs view
@@ -0,0 +1,28 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module CRDT.Cv.GSet+    ( GSet+    , add+    , initial+    , query+    ) where++import           Data.Semilattice (Semilattice)+import           Data.Set (Set)+import qualified Data.Set as Set++-- | Grow-only set+type GSet = Set++instance Ord a => Semilattice (Set a)++-- | update+add :: Ord a => a -> GSet a -> GSet a+add = Set.insert++-- | initialization+initial :: GSet a+initial = Set.empty++query :: Ord a => a -> GSet a -> Bool+query = Set.member
+ lib/CRDT/Cv/LWW.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}++module CRDT.Cv.LWW+    ( LWW (..)+    , initial+    , assign+    , query+    ) where++import           Data.Function (on)+import           Data.Semigroup (Semigroup, (<>))+import           Lens.Micro ((<&>))++import           CRDT.Cv (CvRDT)+import           LamportClock (Clock (newTimestamp), Timestamp)++-- | Last write wins. Assuming timestamp is unique.+data LWW a = LWW+    { timestamp :: !Timestamp+    , value     :: !a+    }+    deriving (Show)++instance Eq (LWW a) where+    (==) = (==) `on` timestamp++instance Ord (LWW a) where+    (<=) = (<=) `on` timestamp++-- | Merge by choosing more recent timestamp.+instance Semigroup (LWW a) where+    (<>) = max++instance CvRDT (LWW a)++-- | Initialize state+initial :: Clock f => a -> f (LWW a)+initial value = newTimestamp <&> \timestamp -> LWW{timestamp = timestamp, value}++-- | Change state+assign :: Clock f => a -> LWW a -> f (LWW a)+assign value cur = newTimestamp <&> \timestamp -> cur <> LWW{timestamp, value}++-- | Query state+query :: LWW a -> a+query = value
+ lib/CRDT/Cv/Max.hs view
@@ -0,0 +1,19 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module CRDT.Cv.Max+    ( Max (..)+    , point+    , query+    ) where++import           Data.Semigroup (Max (..))+import           Data.Semilattice (Semilattice)++instance Ord a => Semilattice (Max a)++-- | Construct new value+point :: a -> Max a+point = Max++query :: Max a -> a+query = getMax
+ lib/CRDT/Cv/PNCounter.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE NamedFieldPuns #-}++module CRDT.Cv.PNCounter+    ( PNCounter (..)+    , initial+    , query+    -- * Operations+    , decrement+    , increment+    ) where++import           Data.Semigroup (Semigroup (..))++import           CRDT.Cv (CvRDT)+import           CRDT.Cv.GCounter (GCounter)+import qualified CRDT.Cv.GCounter as GCounter++{- |+Positive-negative counter. Allows incrementing and decrementing.+Nice example of combining of existing CvRDT ('GCounter' in this case)+to create another CvRDT.+-}+data PNCounter a = PNCounter+    { positive :: !(GCounter a)+    , negative :: !(GCounter a)+    }+    deriving (Eq, Show)++instance Ord a => Semigroup (PNCounter a) where+    PNCounter p1 n1 <> PNCounter p2 n2 = PNCounter (p1 <> p2) (n1 <> n2)++instance Ord a => CvRDT (PNCounter a)++-- | Get value from the state+query :: Num a => PNCounter a -> a+query PNCounter{positive, negative} =+    GCounter.query positive - GCounter.query negative++-- | Decrement counter+decrement+    :: Num a+    => Word -- ^ replica id+    -> PNCounter a+    -> PNCounter a+decrement i pnc@PNCounter{negative} =+    pnc{negative = GCounter.increment i negative}++-- | Increment counter+increment+    :: Num a+    => Word -- ^ replica id+    -> PNCounter a+    -> PNCounter a+increment i pnc@PNCounter{positive} =+    pnc{positive = GCounter.increment i positive}++-- | Initial state+initial :: PNCounter a+initial = PNCounter{positive = GCounter.initial, negative = GCounter.initial}
− lib/CRDT/GCounter/Cm.hs
@@ -1,22 +0,0 @@-{-# LANGUAGE TypeFamilies #-}--module CRDT.GCounter.Cm-    ( GCounter (..)-    , initial-    ) where--import           CRDT.Cm (CmRDT, State, update)---- | Grow-only counter.------ Commutativity: 'Increment' obviously commutes with itself.-data GCounter a = Increment-    deriving (Show)--instance Num a => CmRDT (GCounter a) where-    type State (GCounter a) = a-    update _ = (+1)---- | Initial state-initial :: Num a => a-initial = 0
− lib/CRDT/GCounter/Cv.hs
@@ -1,31 +0,0 @@-module CRDT.GCounter.Cv-    ( GCounter-    , initial-    , query-    -- * Operation-    , increment-    ) where--import qualified Data.IntMap.Strict as IntMap--import           CRDT.GCounter.Cv.Internal---- | Increment counter-increment-    :: Num a-    => Word -- ^ replica id-    -> GCounter a-    -> GCounter a-increment replicaId (GCounter imap) = GCounter (IntMap.insertWith (+) i 1 imap)-  where-    i = fromIntegral replicaId---- | Initial state-initial :: GCounter a-initial = GCounter IntMap.empty---- | Get value from the state-query :: Num a => GCounter a -> a-query (GCounter v) = mapSum v-  where-    mapSum = IntMap.foldr (+) 0
− lib/CRDT/GCounter/Cv/Internal.hs
@@ -1,16 +0,0 @@-module CRDT.GCounter.Cv.Internal where--import           Data.Semigroup (Semigroup ((<>)))-import           Data.IntMap.Strict (IntMap)-import qualified Data.IntMap.Strict as IntMap--import CRDT.Cv (CvRDT)---- | Grow-only counter.-newtype GCounter a = GCounter (IntMap a)-    deriving (Eq, Show)--instance Ord a => Semigroup (GCounter a) where-    GCounter x <> GCounter y = GCounter $ IntMap.unionWith max x y--instance Ord a => CvRDT (GCounter a)
− lib/CRDT/GSet/Cv.hs
@@ -1,21 +0,0 @@-module CRDT.GSet.Cv-    ( GSet-    , add-    , initial-    , query-    ) where--import qualified Data.Set as Set--import           CRDT.GSet.Cv.Internal---- | update-add :: Ord a => a -> GSet a -> GSet a-add = Set.insert---- | initialization-initial :: GSet a-initial = Set.empty--query :: Ord a => a -> GSet a -> Bool-query = Set.member
− lib/CRDT/GSet/Cv/Internal.hs
@@ -1,12 +0,0 @@-{-# OPTIONS_GHC -Wno-orphans #-}--module CRDT.GSet.Cv.Internal where--import Data.Set (Set)--import CRDT.Cv  (CvRDT)---- | Grow-only set-type GSet = Set--instance Ord a => CvRDT (Set a)
− lib/CRDT/LWW.hs
@@ -1,44 +0,0 @@-{-# LANGUAGE TypeFamilies #-}--module CRDT.LWW-    ( LWW (..)-    , point-    , write-    , query-    ) where--import           Data.Semigroup (Semigroup (..))--import           CRDT.Cm (CmRDT, State)-import qualified CRDT.Cm as Cm-import           CRDT.Cv (CvRDT)-import           CRDT.Timestamp (Timestamp)---- | Last write wins. Interesting, this type is both 'CmRDT' and 'CvRDT'.-data LWW a = Write-    { timestamp :: !Timestamp-    , value     :: !a-    }-    deriving (Eq, Ord, Show)---- | Merge by choosing more recent timestamp.-instance Ord a => Semigroup (LWW a) where-    (<>) = max--instance Ord a => CmRDT (LWW a) where-    type State (LWW a) = LWW a-    update = max--instance Ord a => CvRDT (LWW a)---- | Initialize state-point :: Timestamp -> a -> LWW a-point = Write---- | Change state-write :: Ord a => Timestamp -> a -> LWW a -> LWW a-write t v s = Write t v <> s---- | Query state-query :: LWW a -> a-query = value
− lib/CRDT/PNCounter/Cm.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE TypeFamilies #-}--module CRDT.PNCounter.Cm-    ( PNCounter (..)-    , initial-    ) where--import CRDT.Cm (CmRDT, State, update)---- | Positive-negative counter. Allows incrementing and decrementing.-data PNCounter a = Increment | Decrement-    deriving (Bounded, Enum, Show)--instance Num a => CmRDT (PNCounter a) where-    type State (PNCounter a) = a-    update = \case-        Increment -> (+1)-        Decrement -> subtract 1---- | Initial state-initial :: Num a => a-initial = 0
− lib/CRDT/PNCounter/Cv.hs
@@ -1,41 +0,0 @@-{-# LANGUAGE NamedFieldPuns #-}--module CRDT.PNCounter.Cv-    ( PNCounter-    , initial-    , query-    -- * Operations-    , decrement-    , increment-    ) where--import qualified CRDT.GCounter.Cv as GCounter--import CRDT.PNCounter.Cv.Internal---- | Get value from the state-query :: Num a => PNCounter a -> a-query PNCounter{positive, negative} =-    GCounter.query positive - GCounter.query negative---- | Decrement counter-decrement-    :: Num a-    => Word -- ^ replica id-    -> PNCounter a-    -> PNCounter a-decrement i pnc@PNCounter{negative} =-    pnc{negative = GCounter.increment i negative}---- | Increment counter-increment-    :: Num a-    => Word -- ^ replica id-    -> PNCounter a-    -> PNCounter a-increment i pnc@PNCounter{positive} =-    pnc{positive = GCounter.increment i positive}---- | Initial state-initial :: PNCounter a-initial = PNCounter{positive = GCounter.initial, negative = GCounter.initial}
− lib/CRDT/PNCounter/Cv/Internal.hs
@@ -1,22 +0,0 @@-module CRDT.PNCounter.Cv.Internal where--import Data.Semigroup (Semigroup (..))--import CRDT.Cv          (CvRDT)-import CRDT.GCounter.Cv (GCounter)--{- |-Positive-negative counter. Allows incrementing and decrementing.-Nice example of combining of existing CvRDT ('GCounter' in this case)-to create another CvRDT.--}-data PNCounter a = PNCounter-    { positive :: !(GCounter a)-    , negative :: !(GCounter a)-    }-    deriving (Eq, Show)--instance Ord a => Semigroup (PNCounter a) where-    PNCounter p1 n1 <> PNCounter p2 n2 = PNCounter (p1 <> p2) (n1 <> n2)--instance Ord a => CvRDT (PNCounter a)
− lib/CRDT/Timestamp.hs
@@ -1,7 +0,0 @@-module CRDT.Timestamp-    ( Timestamp-    ) where--import           Numeric.Natural (Natural)--type Timestamp = Natural
+ lib/Data/Observe.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE TypeFamilies #-}++module Data.Observe+    ( Observe (..)+    ) where++import           Data.Kind (Type)++class Observe a where+    type Observed a :: Type+    observe :: a -> Observed a
lib/Data/Semilattice.hs view
@@ -1,6 +1,6 @@ module Data.Semilattice     ( Semilattice-    , slappend+    , merge     ) where  import           Data.Semigroup (Semigroup, (<>))@@ -25,7 +25,7 @@ class Semigroup a => Semilattice a  -- | Just ('Semigroup.<>'), specialized to 'Semilattice'.-slappend :: Semilattice a => a -> a -> a-slappend = (<>)-infixr 6 `slappend`-{-# INLINE slappend #-}+merge :: Semilattice a => a -> a -> a+merge = (<>)+infixr 6 `merge`+{-# INLINE merge #-}
+ lib/LamportClock.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeSynonymInstances #-}++module LamportClock+    ( Pid (..)+    , Time+    , Timestamp (..)+    , Clock (..)+    , LamportClock+    , runLamportClock+    , Process+    , runProcess+    , barrier+    ) where++import           Control.Monad.Reader (ReaderT, ask, runReaderT)+import           Control.Monad.State.Strict (State, evalState, modify)+import           Data.EnumMap.Strict (EnumMap)+import qualified Data.EnumMap.Strict as EnumMap+import           Data.Functor (($>))+import           Data.Maybe (fromMaybe)+import           Data.Word (Word32)+import           Lens.Micro (at, non)+import           Lens.Micro.Extra ((<<+=))++type Time = Word++-- | Unique process identifier+newtype Pid = Pid Word32+    deriving (Enum, Eq, Ord, Show)++-- | TODO(cblp, 2017-09-28) Use bounded-intmap+type LamportClock = State (EnumMap Pid Time)++-- | XXX Make sure all subsequent calls to 'newTimestamp' return timestamps+-- greater than all prior calls.+barrier :: [Pid] -> LamportClock ()+barrier pids =+    modify $ \clocks -> let+        selectedClocks = EnumMap.fromList+            [(pid, fromMaybe 0 $ EnumMap.lookup pid clocks) | pid <- pids]+        in+        if null selectedClocks then+            clocks+        else+            EnumMap.union+                (selectedClocks $> succ (maximum selectedClocks))+                clocks++-- | Timestamps are assumed unique, totally ordered,+-- and consistent with causal order;+-- i.e., if assignment 1 happened-before assignment 2,+-- the former’s timestamp is less than the latter’s.+data Timestamp = Timestamp !Time !Pid+    deriving (Eq, Ord, Show)++class Applicative f => Clock f where+    -- | Get another unique timestamp+    newTimestamp :: f Timestamp++type Process = ReaderT Pid LamportClock++instance Clock Process where+    newTimestamp = do+        pid <- ask+        time <- at pid . non 0 <<+= 1+        pure $ Timestamp time pid++runLamportClock :: LamportClock a -> a+runLamportClock action = evalState action mempty++runProcess :: Pid -> Process a -> LamportClock a+runProcess pid action = runReaderT action pid
+ lib/Lens/Micro/Extra.hs view
@@ -0,0 +1,29 @@+{-# OPTIONS_GHC -Wno-orphans #-}++{-# LANGUAGE TypeFamilies #-}++module Lens.Micro.Extra ((<<+=)) where++import           Control.Monad.State (MonadState)+import           Data.EnumMap.Strict (EnumMap, enumMapToIntMap, intMapToEnumMap)+import           Lens.Micro (LensLike', at, ix)+import           Lens.Micro.GHC ()+import           Lens.Micro.Internal (At, Index, IxValue, Ixed)+import           Lens.Micro.Mtl ((<<%=))++(<<+=) :: (Num a, MonadState s m) => LensLike' ((,) a) s a -> a -> m a+alens <<+= n = alens <<%= (+ n)+infix 4 <<+=+{-# INLINE (<<+=) #-}++type instance Index (EnumMap k v) = k++type instance IxValue (EnumMap k v) = v++instance Enum k => Ixed (EnumMap k v) where+    ix k f m = intMapToEnumMap <$> ix (fromEnum k) f (enumMapToIntMap m)+    {-# INLINE ix #-}++instance Enum k => At (EnumMap k v) where+    at k f m = intMapToEnumMap <$> at (fromEnum k) f (enumMapToIntMap m)+    {-# INLINE at #-}
+ test/ArbitraryOrphans.hs view
@@ -0,0 +1,47 @@+{-# OPTIONS_GHC -Wno-orphans #-}++{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE StandaloneDeriving #-}++module ArbitraryOrphans () where++import           Test.QuickCheck (Arbitrary (..), arbitraryBoundedEnum, oneof)++import           CRDT.Cm.Counter (Counter (..), CounterOp (..))+import           CRDT.Cm.GSet (Add (..))+import           CRDT.Cm.LWW (Assign (..), LWW (..))+import           CRDT.Cm.TPSet (TPSet (..), TPSetOp (..))+import qualified CRDT.Cm.TPSet as TPSet+import           CRDT.Cv.GCounter (GCounter (..))+import           CRDT.Cv.Max (Max (..))+import           CRDT.Cv.PNCounter (PNCounter (..))+import           LamportClock (Pid (..), Timestamp (..))++deriving instance Arbitrary a => Arbitrary (Counter a)++instance Arbitrary (CounterOp a) where+    arbitrary = arbitraryBoundedEnum++deriving instance Arbitrary a => Arbitrary (Add a)++deriving instance Arbitrary a => Arbitrary (Assign a)++instance Arbitrary a => Arbitrary (LWW a) where+    arbitrary = LWW <$> arbitrary <*> arbitrary++deriving instance (Arbitrary a, Ord a) => Arbitrary (TPSet a)++instance Arbitrary a => Arbitrary (TPSetOp a) where+    arbitrary = oneof [TPSet.Add <$> arbitrary, Remove <$> arbitrary]++deriving instance Arbitrary a => Arbitrary (GCounter a)++deriving instance Arbitrary a => Arbitrary (Max a)++instance Arbitrary a => Arbitrary (PNCounter a) where+    arbitrary = PNCounter <$> arbitrary <*> arbitrary++deriving instance Arbitrary Pid++instance Arbitrary Timestamp where+    arbitrary = Timestamp <$> arbitrary <*> arbitrary
+ test/Counter.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE TypeApplications #-}++module Counter+    ( counter+    ) where++import           CRDT.Cm.Counter (Counter)+import           Test.Tasty (TestTree, testGroup)++import           Laws (cmrdtLaw)++counter :: TestTree+counter = testGroup "Counter" [cmrdtLaw @(Counter Int)]
test/GCounter.hs view
@@ -1,42 +1,21 @@-{-# OPTIONS_GHC -Wno-orphans #-}--{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-}  module GCounter     ( gCounter     ) where -import           Test.QuickCheck (Arbitrary, arbitrary) import           Test.Tasty (TestTree, testGroup) import           Test.Tasty.QuickCheck (testProperty) -import           CRDT.Cm (update)-import qualified CRDT.GCounter.Cm as Cm-import qualified CRDT.GCounter.Cv as Cv-import qualified CRDT.GCounter.Cv.Internal as Cv--import           Laws (cmrdtLaws, cvrdtLaws)--instance Arbitrary (Cm.GCounter a) where-    arbitrary = pure Cm.Increment+import           CRDT.Cv.GCounter (GCounter (..), increment, query) -deriving instance Arbitrary a => Arbitrary (Cv.GCounter a)+import           Laws (cvrdtLaws)  gCounter :: TestTree gCounter = testGroup "GCounter"-    [ testGroup "Cv"-        [ cvrdtLaws @(Cv.GCounter Int)-        , testProperty "increment" $-            \(counter :: Cv.GCounter Int) i ->-                Cv.query (Cv.increment i counter)-                == succ (Cv.query counter)-        ]-    , testGroup "Cm"-        [ cmrdtLaws @(Cm.GCounter Int)-        , testProperty "increment" $-            \(counter :: Int) -> update Cm.Increment counter == succ counter-        ]+    [ cvrdtLaws @(GCounter Int)+    , testProperty "increment" $+        \(counter :: GCounter Int) i ->+            query (increment i counter) == succ (query counter)     ]
test/GSet.hs view
@@ -1,5 +1,3 @@-{-# OPTIONS_GHC -Wno-orphans #-}- {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -10,16 +8,15 @@ import           Test.Tasty (TestTree, testGroup) import           Test.Tasty.QuickCheck (testProperty, (==>)) -import qualified CRDT.GSet.Cv as Cv+import           CRDT.Cm.GSet (GSet)+import           CRDT.Cv.GSet (add, query) -import           Laws (cvrdtLaws)+import           Laws (cmrdtLaw, cvrdtLaws)  gSet :: TestTree gSet = testGroup "GSet"-    [ testGroup "Cv"-        [ cvrdtLaws @(Cv.GSet Int)-        , testProperty "add" $-            \(set :: Cv.GSet Int) i ->-                not (Cv.query i set) ==> Cv.query i (Cv.add i set)-        ]+    [ cmrdtLaw @(GSet Int)+    , cvrdtLaws @(GSet Int)+    , testProperty "Cv.add" $+        \(set :: GSet Int) i -> not (query i set) ==> query i (add i set)     ]
test/LWW.hs view
@@ -1,57 +1,38 @@-{-# OPTIONS_GHC -Wno-orphans #-}- {-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}  module LWW (lww) where -import           Test.QuickCheck (Arbitrary, arbitrary)+import           Data.Semigroup ((<>)) import           Test.Tasty (TestTree, testGroup)-import           Test.Tasty.QuickCheck (Positive (..), testProperty)--import           CRDT.Cm (update)-import           CRDT.LWW (LWW (Write), point, query, write)+import           Test.Tasty.QuickCheck (testProperty) -import           Laws (cmrdtLaws, cvrdtLaws)+import           CRDT.Cm.LWW (LWW (..))+import           CRDT.Cv.LWW (assign, initial, query)+import           LamportClock (barrier, runLamportClock, runProcess) -instance Arbitrary a => Arbitrary (LWW a) where-    arbitrary = Write <$> arbitrary <*> arbitrary+import           Laws (cmrdtLaw, cvrdtLaws)  lww :: TestTree lww = testGroup "LWW"-    [ testGroup "Cm"-        [ cmrdtLaws @(LWW Int)-        , testProperty "write latter" $-            \formerTime (formerValue :: Int) (Positive dt) latterValue -> let-                latterTime = formerTime + dt-                state = point formerTime formerValue-                state' = update (Write latterTime latterValue) state-                in-                query state' == latterValue-        , testProperty "write former" $-            \formerTime (formerValue :: Int) (Positive dt) latterValue -> let-                latterTime = formerTime + dt-                state = point latterTime latterValue-                state' = update (Write formerTime formerValue) state-                in-                query state' == latterValue-        ]+    [ cmrdtLaw @(LWW Int)     , testGroup "Cv"         [ cvrdtLaws @(LWW Int)-        , testProperty "write latter" $-            \formerTime (formerValue :: Int) (Positive dt) latterValue -> let-                latterTime = formerTime + dt-                state = point formerTime formerValue-                state' = write latterTime latterValue state-                in-                query state' == latterValue-        , testProperty "write former" $-            \formerTime (formerValue :: Int) (Positive dt) latterValue -> let-                latterTime = formerTime + dt-                state = point latterTime latterValue-                state' = write formerTime formerValue state-                in-                query state' == latterValue+        , testProperty "assign" $+            \pid1 pid2 (formerValue :: Int) latterValue ->+                runLamportClock $ do+                    state1 <- runProcess pid1 $ initial formerValue+                    barrier [pid1, pid2]+                    state2 <- runProcess pid2 $ assign latterValue state1+                    pure $ query state2 == latterValue+        , testProperty "merge with former" $+            \pid1 pid2 (formerValue :: Int) latterValue ->+                runLamportClock $ do+                    state1 <- runProcess pid1 $ initial formerValue+                    barrier [pid1, pid2]+                    state2 <- runProcess pid2 $ initial latterValue+                    pure $ query (state1 <> state2) == latterValue         ]     ]
test/Laws.hs view
@@ -1,30 +1,34 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}  module Laws-    ( cmrdtLaws+    ( cmrdtLaw     , cvrdtLaws-    , semigroupLaw-    , semilatticeLaws     ) where +import           Control.Monad (unless)+import           Data.Function ((&))+import           Data.Observe (Observe (..)) import           Data.Semigroup (Semigroup, (<>))-import           Data.Semilattice (Semilattice, slappend)-import           Test.QuickCheck (Arbitrary)+import           Data.Semilattice (Semilattice, merge) import           Test.Tasty (TestTree, testGroup)-import           Test.Tasty.QuickCheck (testProperty)+import           Test.Tasty.QuickCheck (Arbitrary (..), discard, testProperty,+                                        (===), (==>)) -import           CRDT.Cm (CmRDT, State, update)+import           CRDT.Cm (CmRDT (..), concurrent) import           CRDT.Cv (CvRDT)+import           LamportClock (Clock, runLamportClock, runProcess) +import           ArbitraryOrphans ()+ semigroupLaw :: forall a . (Arbitrary a, Semigroup a, Eq a, Show a) => TestTree semigroupLaw =     testGroup "Semigroup law" [testProperty "associativity" associativity]   where-    associativity :: a -> a -> a -> Bool-    associativity x y z = (x <> y) <> z == x <> (y <> z)+    associativity (x, y, z :: a) = (x <> y) <> z == x <> (y <> z)  semilatticeLaws     :: forall a . (Arbitrary a, Semilattice a, Eq a, Show a) => TestTree@@ -34,23 +38,29 @@     , testProperty "idempotency"   idempotency     ]   where-    commutativity :: a -> a -> Bool-    commutativity x y = x `slappend` y == y `slappend` x--    idempotency :: a -> Bool-    idempotency x = x `slappend` x == x+    commutativity (x, y :: a) = x `merge` y === y `merge` x+    idempotency (x :: a) = x `merge` x === x  cvrdtLaws :: forall a . (Arbitrary a, CvRDT a, Eq a, Show a) => TestTree cvrdtLaws = semilatticeLaws @a -cmrdtLaws-    :: forall op-    . ( Arbitrary op, CmRDT op, Show op-      , Arbitrary (State op), Eq (State op), Show (State op)+cmrdtLaw+    :: forall payload op up+    . ( CmRDT payload op up+      , Arbitrary payload, Show payload+      , Arbitrary op, Show op+      , Show (Observed payload)       )     => TestTree-cmrdtLaws = testProperty "CmRDT law: commutativity" commutativity+cmrdtLaw = testProperty "CmRDT law: concurrent ops commute" $+    \pid (state0 :: payload) (op1, op2 :: op) ->+        runLamportClock $ runProcess pid $ do+            up1 <- updateAtSourceIfCan op1 state0+            up2 <- updateAtSourceIfCan op2 state0+            let state12 = state0 & updateDownstream up1 & updateDownstream up2+            let state21 = state0 & updateDownstream up2 & updateDownstream up1+            pure $ concurrent up1 up2 ==> observe state12 === observe state21   where-    commutativity :: op -> op -> State op -> Bool-    commutativity op1 op2 x =-        (update op1 . update op2) x == (update op2 . update op1) x+    updateAtSourceIfCan :: Clock m => op -> payload -> m up+    updateAtSourceIfCan op state =+        unless (updateAtSourcePre op state) discard *> updateAtSource op
test/Main.hs view
@@ -1,9 +1,13 @@ import           Test.Tasty (defaultMain, testGroup) +import           Counter (counter) import           GCounter (gCounter)+import           GSet (gSet) import           LWW (lww)+import           Max (maxTest) import           PNCounter (pnCounter)-import           GSet      (gSet)+import           TPSet (tpSet)  main :: IO ()-main = defaultMain $ testGroup "" [gCounter, gSet, lww, pnCounter]+main = defaultMain $+    testGroup "" [counter, gCounter, gSet, lww, maxTest, pnCounter, tpSet]
+ test/Max.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Max+    ( maxTest+    ) where++import           Test.Tasty (TestTree, testGroup)+import           Test.Tasty.QuickCheck (testProperty)++import           CRDT.Cv.Max (Max, point, query)+import           Data.Semilattice (merge)++import           Laws (cvrdtLaws)++maxTest :: TestTree+maxTest = testGroup "Max"+    [ testGroup "Cv"+        [ cvrdtLaws @(Max Int)+        , testProperty "merge" $+            \(m :: Max Int) i -> query (point i `merge` m) == max i (query m)+        ]+    ]
test/PNCounter.hs view
@@ -1,5 +1,3 @@-{-# OPTIONS_GHC -Wno-orphans #-}- {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -7,40 +5,21 @@     ( pnCounter     ) where -import           Test.QuickCheck (Arbitrary, arbitrary, arbitraryBoundedEnum) import           Test.Tasty (TestTree, testGroup) import           Test.Tasty.QuickCheck (testProperty) -import           CRDT.Cm (update)-import qualified CRDT.PNCounter.Cm as Cm-import qualified CRDT.PNCounter.Cv as Cv-import qualified CRDT.PNCounter.Cv.Internal as Cv+import           CRDT.Cv.PNCounter (PNCounter (..), decrement, increment, query)  import           GCounter ()-import           Laws (cmrdtLaws, cvrdtLaws)--instance Arbitrary (Cm.PNCounter a) where-    arbitrary = arbitraryBoundedEnum--instance Arbitrary a => Arbitrary (Cv.PNCounter a) where-    arbitrary = Cv.PNCounter <$> arbitrary <*> arbitrary+import           Laws (cvrdtLaws)  pnCounter :: TestTree pnCounter = testGroup "PNCounter"-    [ testGroup "Cv"-        [ cvrdtLaws @(Cv.PNCounter Int)-        , testProperty "increment" $-            \(counter :: Cv.PNCounter Int) i ->-                Cv.query (Cv.increment i counter) == succ (Cv.query counter)-        , testProperty "decrement" $-            \(counter :: Cv.PNCounter Int) i ->-                Cv.query (Cv.decrement i counter) == pred (Cv.query counter)-        ]-    , testGroup "Cm"-        [ cmrdtLaws @(Cm.PNCounter Int)-        , testProperty "increment" $-            \(counter :: Int) -> update Cm.Increment counter == succ counter-        , testProperty "decrement" $-            \(counter :: Int) -> update Cm.Decrement counter == pred counter-        ]+    [ cvrdtLaws @(PNCounter Int)+    , testProperty "increment" $+        \(counter :: PNCounter Int) i ->+            query (increment i counter) == succ (query counter)+    , testProperty "decrement" $+        \(counter :: PNCounter Int) i ->+            query (decrement i counter) == pred (query counter)     ]
+ test/TPSet.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE TypeApplications #-}++module TPSet+    ( tpSet+    ) where++import           CRDT.Cm.TPSet (TPSet)+import           Test.Tasty (TestTree, testGroup)++import           Laws (cmrdtLaw)++tpSet :: TestTree+tpSet = testGroup "TPSet" [cmrdtLaw @(TPSet Int)]