packages feed

crdt 1.0 → 2.0

raw patch · 15 files changed

+195/−280 lines, 15 filesdep −enummapsetdep −latticesdep −microlensPVP ok

version bump matches the API change (PVP)

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

API changes (from Hackage documentation)

- CRDT.Cm.Counter: Counter :: a -> Counter 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: 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: 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.TPSetOp a)
- CRDT.Cm.TPSet: lookup :: Ord a => a -> TPSet a -> Bool
- CRDT.Cm.TPSet: newtype TPSet a
- 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
- 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: }
- LamportClock: instance GHC.Enum.Enum LamportClock.Pid
+ CRDT.Cm: before :: CausalOrd a => a -> a -> Bool
+ CRDT.Cm: class CausalOrd a
+ CRDT.Cm: type Op u = u;
+ CRDT.Cm: type View u = Payload u;
+ CRDT.Cm: type family View u;
+ CRDT.Cm: view :: (CmRDT u, Payload u ~ View u) => Payload u -> View u
+ CRDT.Cm: }
+ CRDT.Cm.Counter: data 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: instance CRDT.Cm.CausalOrd (CRDT.Cm.Counter.Counter a)
+ CRDT.Cm.Counter: instance GHC.Classes.Eq (CRDT.Cm.Counter.Counter a)
+ CRDT.Cm.Counter: instance GHC.Enum.Bounded (CRDT.Cm.Counter.Counter a)
+ CRDT.Cm.Counter: instance GHC.Enum.Enum (CRDT.Cm.Counter.Counter a)
+ CRDT.Cm.Counter: instance GHC.Show.Show (CRDT.Cm.Counter.Counter a)
+ CRDT.Cm.GSet: instance GHC.Classes.Eq a => CRDT.Cm.CausalOrd (CRDT.Cm.GSet.GSet a)
+ CRDT.Cm.GSet: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.GSet.GSet a)
+ CRDT.Cm.GSet: instance GHC.Classes.Ord a => CRDT.Cm.CmRDT (CRDT.Cm.GSet.GSet a)
+ CRDT.Cm.GSet: instance GHC.Show.Show a => GHC.Show.Show (CRDT.Cm.GSet.GSet a)
+ CRDT.Cm.GSet: newtype GSet a
+ CRDT.Cm.TPSet: data TPSet a
+ CRDT.Cm.TPSet: instance GHC.Classes.Eq a => CRDT.Cm.CausalOrd (CRDT.Cm.TPSet.TPSet a)
+ CRDT.Cm.TPSet: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.Cm.TPSet.TPSet a)
+ CRDT.Cm.TPSet: instance GHC.Classes.Ord a => CRDT.Cm.CmRDT (CRDT.Cm.TPSet.TPSet a)
+ CRDT.LWW: Assign :: a -> Assign a
+ CRDT.LWW: LWW :: !a -> !Timestamp -> LWW a
+ CRDT.LWW: [timestamp] :: LWW a -> !Timestamp
+ CRDT.LWW: [value] :: LWW a -> !a
+ CRDT.LWW: assign :: Clock f => a -> LWW a -> f (LWW a)
+ CRDT.LWW: data LWW a
+ CRDT.LWW: initial :: Clock f => a -> f (LWW a)
+ CRDT.LWW: instance CRDT.Cm.CausalOrd (CRDT.LWW.LWW a)
+ CRDT.LWW: instance Data.Semigroup.Semigroup (CRDT.LWW.LWW a)
+ CRDT.LWW: instance Data.Semilattice.Semilattice (CRDT.LWW.LWW a)
+ CRDT.LWW: instance GHC.Classes.Eq (CRDT.LWW.LWW a)
+ CRDT.LWW: instance GHC.Classes.Eq a => CRDT.Cm.CmRDT (CRDT.LWW.LWW a)
+ CRDT.LWW: instance GHC.Classes.Eq a => GHC.Classes.Eq (CRDT.LWW.Assign a)
+ CRDT.LWW: instance GHC.Classes.Ord (CRDT.LWW.LWW a)
+ CRDT.LWW: instance GHC.Show.Show a => GHC.Show.Show (CRDT.LWW.Assign a)
+ CRDT.LWW: instance GHC.Show.Show a => GHC.Show.Show (CRDT.LWW.LWW a)
+ CRDT.LWW: newtype Assign a
+ CRDT.LWW: query :: LWW a -> a
- CRDT.Cm: class (Observe payload, Eq (Observed payload), PartialOrd update) => CmRDT payload op update | payload -> op, op -> update, update -> payload where updateAtSourcePre _ _ = True
+ CRDT.Cm: class (CausalOrd u, Eq (View u)) => CmRDT u where type Op u type Payload u type View u type Op u = u type View u = Payload u updateAtSourcePre _ _ = True updateAtSource = pure view = id where {
- CRDT.Cm: concurrent :: PartialOrd a => a -> a -> Bool
+ CRDT.Cm: concurrent :: CausalOrd a => a -> a -> Bool
- CRDT.Cm: updateAtSource :: (CmRDT payload op update, Clock m) => op -> m update
+ CRDT.Cm: updateAtSource :: (CmRDT u, Clock m, Op u ~ u) => Op u -> m u
- CRDT.Cm: updateAtSourcePre :: CmRDT payload op update => op -> payload -> Bool
+ CRDT.Cm: updateAtSourcePre :: CmRDT u => Op u -> Payload u -> Bool
- CRDT.Cm: updateDownstream :: CmRDT payload op update => update -> payload -> payload
+ CRDT.Cm: updateDownstream :: CmRDT u => u -> Payload u -> Payload u
- CRDT.Cm.Counter: Decrement :: CounterOp a
+ CRDT.Cm.Counter: Decrement :: Counter a
- CRDT.Cm.Counter: Increment :: CounterOp a
+ CRDT.Cm.Counter: Increment :: Counter a
- CRDT.Cm.GSet: Add :: a -> Add a
+ CRDT.Cm.GSet: Add :: a -> GSet a
- CRDT.Cm.TPSet: Add :: a -> TPSetOp a
+ CRDT.Cm.TPSet: Add :: a -> TPSet a
- CRDT.Cm.TPSet: Remove :: a -> TPSetOp a
+ CRDT.Cm.TPSet: Remove :: a -> TPSet a
- CRDT.Cm.TPSet: updateAtSource :: (CmRDT payload op update, Clock m) => op -> m update
+ CRDT.Cm.TPSet: updateAtSource :: (CmRDT u, Clock m) => Op u -> m u
- CRDT.Cm.TPSet: updateDownstream :: CmRDT payload op update => update -> payload -> payload
+ CRDT.Cm.TPSet: updateDownstream :: CmRDT u => u -> Payload u -> Payload u
- LamportClock: Pid :: Word32 -> Pid
+ LamportClock: Pid :: Int -> Pid
- LamportClock: type LamportClock = State (EnumMap Pid Time)
+ LamportClock: type LamportClock = State LamportTime

Files

crdt.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           crdt-version:        1.0+version:        2.0 synopsis:       Conflict-free replicated data types description:    Definitions of CmRDT and CvRDT. Implementations for some classic CRDTs. category:       Distributed Systems@@ -26,29 +26,20 @@   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.Cv.GCounter       CRDT.Cv.GSet-      CRDT.Cv.LWW       CRDT.Cv.Max       CRDT.Cv.PNCounter-      Data.Observe+      CRDT.LWW       Data.Semilattice       LamportClock-  other-modules:-      Lens.Micro.Extra   default-language: Haskell2010  test-suite test
lib/CRDT/Cm.hs view
@@ -1,25 +1,29 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}  module CRDT.Cm-    ( CmRDT (..)+    ( CausalOrd (..)+    , CmRDT (..)     , concurrent     ) where -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+-- | Partial order for causal semantics.+-- Values of some type may be ordered and causally-ordered different ways.+class CausalOrd a where+    before :: a -> a -> Bool +comparable :: CausalOrd a => a -> a -> Bool+comparable a b = a `before` b || b `before` a+ -- | Not comparable, i. e. ¬(a ≤ b) ∧ ¬(b ≤ a).-concurrent :: PartialOrd a => a -> a -> Bool+concurrent :: CausalOrd a => a -> a -> Bool concurrent a b = not $ comparable a b  {- |@@ -55,22 +59,35 @@ Idempotency doesn't need to hold. -} -class (Observe payload, Eq (Observed payload), PartialOrd update)-        => CmRDT payload op update-        | payload -> op, op -> update, update -> payload-        where+class (CausalOrd u, Eq (View u)) => CmRDT u where+    type Op u+    type Op u = u -- default case +    type Payload u++    type View u+    type View u = Payload u -- default case+     -- | Precondition for 'updateAtSource'.     -- Calculates if the operation is applicable to the current state.-    updateAtSourcePre :: op -> payload -> Bool+    updateAtSourcePre :: Op u -> Payload u -> 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+    updateAtSource :: Clock m => Op u -> m u +    default updateAtSource :: (Clock m, Op u ~ u) => Op u -> m u+    updateAtSource = pure+     -- | Apply an update to the payload.     -- An invalid update must be ignored.-    updateDownstream :: update -> payload -> payload+    updateDownstream :: u -> Payload u -> Payload u++    -- | Extract user-visible value from payload+    view :: Payload u -> View u++    default view :: (Payload u ~ View u) => Payload u -> View u+    view = id
lib/CRDT/Cm/Counter.hs view
@@ -1,41 +1,24 @@ {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-} {-# 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)+import           CRDT.Cm (CausalOrd (..), CmRDT (..)) -data CounterOp a = Increment | Decrement+data Counter 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 (Num a, Eq a) => CmRDT (Counter a) where+    type Payload  (Counter a) = a -instance Observe (Counter a) where-    type Observed (Counter a) = a-    observe (Counter c) = c+    updateDownstream = \case+        Increment -> (+ 1)+        Decrement -> subtract 1  -- | 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+instance CausalOrd (Counter a) where+    before _ _ = False
lib/CRDT/Cm/GSet.hs view
@@ -1,43 +1,25 @@-{-# OPTIONS_GHC -Wno-orphans #-}- {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-}  module CRDT.Cm.GSet-    ( GSet-    , Add (..)-    , initial-    , lookup+    ( GSet (..)     ) 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+import           CRDT.Cm (CausalOrd (..), CmRDT (..)) -newtype Add a = Add a+newtype GSet 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 Ord a => CmRDT (GSet a) where+    type Payload  (GSet a) = Set a -instance Observe (GSet a) where-    type Observed (GSet a) = Set a-    observe = id+    updateDownstream (Add a) = Set.insert a -instance Eq a => PartialOrd (Add a) where-    leq _ _ = False+instance Eq a => CausalOrd (GSet a) where+    before _ _ = False
− lib/CRDT/Cm/LWW.hs
@@ -1,35 +0,0 @@-{-# 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
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TypeFamilies #-}@@ -5,50 +6,29 @@ -- | 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)+import           CRDT.Cm (CausalOrd (..), CmRDT (..)) -data TPSetOp a = Add a | Remove a+data TPSet 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) where+    type Payload  (TPSet a) = Set a -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}+        Remove a  -> Set.member a payload -instance Observe (TPSet a) where-    type Observed (TPSet a) = Set a-    observe = payload+    updateDownstream = \case+        Add a     -> Set.insert a+        Remove a  -> Set.delete a -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+instance Eq a => CausalOrd (TPSet a) where+    Remove a `before` Add b = a == b -- `Remove e` can occur only after `Add e`+    _        `before` _     = False  -- Any other are not ordered
− lib/CRDT/Cv/LWW.hs
@@ -1,48 +0,0 @@-{-# 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/LWW.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}++module CRDT.LWW+    ( LWW (..)+      -- * CvRDT+    , initial+    , assign+    , query+      -- * CmRDT+    , Assign (..)+    ) where++import           Data.Function (on)+import           Data.Semigroup (Semigroup, (<>))++import           CRDT.Cm (CausalOrd (..), CmRDT (..))+import           CRDT.Cv (CvRDT)+import           LamportClock (Clock (newTimestamp), Timestamp)++-- | Last write wins. Assuming timestamp is unique.+-- This type is both 'CmRDT' and 'CvRDT'.+data LWW a = LWW+    { value     :: !a+    , timestamp :: !Timestamp+    }+    deriving (Show)++--------------------------------------------------------------------------------+-- CvRDT -----------------------------------------------------------------------++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 = LWW value <$> newTimestamp++-- | Change state as CvRDT operation.+-- Current value is ignored, because new timestamp is always greater.+assign :: Clock f => a -> LWW a -> f (LWW a)+assign value _ = LWW value <$> newTimestamp++-- | Query state+query :: LWW a -> a+query = value++--------------------------------------------------------------------------------+-- CmRDT -----------------------------------------------------------------------++instance CausalOrd (LWW a) where+    before _ _ = False++-- | Change state as CmRDT operation+newtype Assign a = Assign a+    deriving (Eq, Show)++instance Eq a => CmRDT (LWW a) where+    type Op       (LWW a) = Assign a+    type Payload  (LWW a) = LWW a+    type View     (LWW a) = a++    updateAtSource (Assign value) = LWW value <$> newTimestamp++    updateDownstream = (<>)++    view = value
− lib/Data/Observe.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE TypeFamilies #-}--module Data.Observe-    ( Observe (..)-    ) where--import           Data.Kind (Type)--class Observe a where-    type Observed a :: Type-    observe :: a -> Observed a
lib/LamportClock.hs view
@@ -1,7 +1,5 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeSynonymInstances #-}  module LamportClock     ( Pid (..)@@ -15,37 +13,42 @@     , barrier     ) where +import           Control.Arrow (first) 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           Control.Monad.State.Strict (MonadState, State, evalState,+                                             modify, state) import           Data.Functor (($>))+import           Data.IntMap.Strict (IntMap)+import qualified Data.IntMap.Strict as IntMap 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)+newtype Pid = Pid Int+    deriving (Eq, Ord, Show) --- | TODO(cblp, 2017-09-28) Use bounded-intmap-type LamportClock = State (EnumMap Pid Time)+unPid :: Pid -> Int+unPid (Pid pid) = pid +-- | Key is 'Pid'. Non-present value is equivalent to 0.+-- TODO(cblp, 2017-09-28) Use bounded-intmap+type LamportTime = IntMap Time++type LamportClock = State LamportTime+ -- | 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]+        selectedClocks = lamportTimeFromList+            [(pid, fromMaybe 0 $ lamportTimeLookup pid clocks) | pid <- pids]         in         if null selectedClocks then             clocks         else-            EnumMap.union+            IntMap.union                 (selectedClocks $> succ (maximum selectedClocks))                 clocks @@ -65,7 +68,7 @@ instance Clock Process where     newTimestamp = do         pid <- ask-        time <- at pid . non 0 <<+= 1+        time <- postIncrementAt pid         pure $ Timestamp time pid  runLamportClock :: LamportClock a -> a@@ -73,3 +76,14 @@  runProcess :: Pid -> Process a -> LamportClock a runProcess pid action = runReaderT action pid++postIncrementAt :: MonadState LamportTime m => Pid -> m Time+postIncrementAt (Pid pid) = state $ \m ->+    let v = fromMaybe 0 $ IntMap.lookup pid m+    in  (v, IntMap.insert pid (v + 1) m)++lamportTimeFromList :: [(Pid, Time)] -> LamportTime+lamportTimeFromList = IntMap.fromList . map (first unPid)++lamportTimeLookup :: Pid -> LamportTime -> Maybe Time+lamportTimeLookup = IntMap.lookup . unPid
− lib/Lens/Micro/Extra.hs
@@ -1,29 +0,0 @@-{-# 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
@@ -7,31 +7,27 @@  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           CRDT.Cm.Counter (Counter (..))+import           CRDT.Cm.GSet (GSet (..))+import           CRDT.Cm.TPSet (TPSet (..)) import qualified CRDT.Cm.TPSet as TPSet import           CRDT.Cv.GCounter (GCounter (..)) import           CRDT.Cv.Max (Max (..)) import           CRDT.Cv.PNCounter (PNCounter (..))+import           CRDT.LWW (Assign (..), LWW (..)) import           LamportClock (Pid (..), Timestamp (..)) -deriving instance Arbitrary a => Arbitrary (Counter a)--instance Arbitrary (CounterOp a) where+instance Arbitrary (Counter 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)+deriving instance Arbitrary a => Arbitrary (GSet a) -instance Arbitrary a => Arbitrary (TPSetOp a) where+instance Arbitrary a => Arbitrary (TPSet a) where     arbitrary = oneof [TPSet.Add <$> arbitrary, Remove <$> arbitrary]  deriving instance Arbitrary a => Arbitrary (GCounter a)
test/GSet.hs view
@@ -8,15 +8,15 @@ import           Test.Tasty (TestTree, testGroup) import           Test.Tasty.QuickCheck (testProperty, (==>)) -import           CRDT.Cm.GSet (GSet)-import           CRDT.Cv.GSet (add, query)+import qualified CRDT.Cm.GSet as Cm+import qualified CRDT.Cv.GSet as Cv  import           Laws (cmrdtLaw, cvrdtLaws)  gSet :: TestTree gSet = testGroup "GSet"-    [ cmrdtLaw @(GSet Int)-    , cvrdtLaws @(GSet Int)-    , testProperty "Cv.add" $-        \(set :: GSet Int) i -> not (query i set) ==> query i (add i set)+    [ cmrdtLaw @(Cm.GSet Int)+    , cvrdtLaws @(Cv.GSet Int)+    , testProperty "Cv.add" $ \(set :: Cv.GSet Int) i ->+        not (Cv.query i set) ==> Cv.query i (Cv.add i set)     ]
test/LWW.hs view
@@ -9,8 +9,7 @@ import           Test.Tasty (TestTree, testGroup) import           Test.Tasty.QuickCheck (testProperty) -import           CRDT.Cm.LWW (LWW (..))-import           CRDT.Cv.LWW (assign, initial, query)+import           CRDT.LWW (LWW (..), assign, initial, query) import           LamportClock (barrier, runLamportClock, runProcess)  import           Laws (cmrdtLaw, cvrdtLaws)
test/Laws.hs view
@@ -11,7 +11,6 @@  import           Control.Monad (unless) import           Data.Function ((&))-import           Data.Observe (Observe (..)) import           Data.Semigroup (Semigroup, (<>)) import           Data.Semilattice (Semilattice, merge) import           Test.Tasty (TestTree, testGroup)@@ -45,22 +44,22 @@ cvrdtLaws = semilatticeLaws @a  cmrdtLaw-    :: forall payload op up-    . ( CmRDT payload op up-      , Arbitrary payload, Show payload-      , Arbitrary op, Show op-      , Show (Observed payload)+    :: forall u+    . ( CmRDT u+      , Arbitrary (Op u), Show (Op u)+      , Arbitrary (Payload u), Show (Payload u)+      , Show (View u)       )     => TestTree cmrdtLaw = testProperty "CmRDT law: concurrent ops commute" $-    \pid (state0 :: payload) (op1, op2 :: op) ->+    \pid (state0 :: Payload u) (op1, op2 :: Op u) ->         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+            pure $ concurrent up1 up2 ==> view @u state12 === view @u state21   where-    updateAtSourceIfCan :: Clock m => op -> payload -> m up+    updateAtSourceIfCan :: Clock m => Op u -> Payload u -> m u     updateAtSourceIfCan op state =-        unless (updateAtSourcePre op state) discard *> updateAtSource op+        unless (updateAtSourcePre @u op state) discard *> updateAtSource op