{- | module: Network.Riak.CRDT copyright: (c) 2016 Sentenai author: Antonio Nikishaev license: Apache CRDT operations * Haskell-side * Haskell values: 'Counter', 'Set' etc * ADT for operations: 'CounterOp', 'SetOp' etc * 'modify' to locally modify a value (matching riak-side behaviour) * Riak-side * 'get' to get a current value * 'sendModify' to ask Riak to apply modifications TL;DR example >>> let c = Counter 41 >>> let op = CounterInc 1 >>> modify op c Counter 42 >>> get conn "counters" "bucket" "key" Just (DTCounter (Counter 41)) >>> sendModify conn "counters" "bucket" "key" [op] >> get conn "counters" "bucket" "key" Just (DTCounter (Counter 42)) -} {-# LANGUAGE TypeFamilies, OverloadedStrings, ScopedTypeVariables, PatternGuards #-} {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-} module Network.Riak.CRDT (module Network.Riak.CRDT.Types, get, CRDT(..)) where import Data.Default.Class import qualified Data.Map as M import Data.Proxy import Data.Semigroup import qualified Data.Set as S import Network.Riak.CRDT.Ops import Network.Riak.CRDT.Riak import Network.Riak.CRDT.Types import Network.Riak.Types -- | Modify a counter by applying operations ops modifyCounter :: CounterOp -> Counter -> Counter modifyCounter op c = c <> Counter i where CounterInc i = op -- | Modify a set by applying operations ops modifySet :: SetOp -> Set -> Set modifySet op (Set c) = Set (c `S.union` adds S.\\ rems) where SetOpsComb adds rems = toOpsComb op modifyMap :: MapOp -> Map -> Map modifyMap (MapRemove field) (Map mc) = Map $ M.delete field mc modifyMap (MapUpdate path op) m = modifyMap1 path op m modifyMap1 :: MapPath -> MapValueOp -> Map -> Map modifyMap1 (MapPath (e :| [])) op m = modMap mf op m where mf = MapField (mapEntryTag op) e modifyMap1 (MapPath (e :| (r:rs))) op (Map m') = Map $ M.alter (Just . f) (MapField MapMapTag e) m' where f :: Maybe MapEntry -> MapEntry f Nothing = f (Just $ MapMap def) f (Just (MapMap m)) = MapMap . modifyMap1 (MapPath (r :| rs)) op $ m f (Just z) = z modMap :: MapField -> MapValueOp -> Map -> Map modMap ix op (Map m) = Map $ M.alter (Just . modifyMapValue op) ix m modifyMapValue :: MapValueOp -> Maybe MapEntry -> MapEntry modifyMapValue (MapSetOp op) = modifyEntry (Proxy :: Proxy Set) op modifyMapValue (MapCounterOp op) = modifyEntry (Proxy :: Proxy Counter) op modifyMapValue (MapMapOp op) = modifyEntry (Proxy :: Proxy Map) op modifyMapValue (MapFlagOp op) = modifyEntry (Proxy :: Proxy Flag) op modifyMapValue (MapRegisterOp op) = modifyEntry (Proxy :: Proxy Register) op modifyFlag :: FlagOp -> Flag -> Flag modifyFlag (FlagSet x) = const (Flag x) modifyRegister :: RegisterOp -> Register -> Register modifyRegister (RegisterSet x) = const (Register x) -- | Types that can be held inside 'Map' class Default a => MapCRDT a where type MapOperation_ a :: * mapModify :: MapOperation_ a -> a -> a -- | modify a maybe-absent 'MapEntry' modifyEntry :: Proxy a -> MapOperation_ a -> Maybe MapEntry -> MapEntry modifyEntry _ op Nothing = toEntry . mapModify op $ (def :: a) modifyEntry _ op (Just e) | Just v <- fromEntry e = toEntry . mapModify op $ (v :: a) | otherwise = e toEntry :: a -> MapEntry fromEntry :: MapEntry -> Maybe a instance MapCRDT Flag where type MapOperation_ Flag = FlagOp mapModify = modifyFlag fromEntry (MapFlag f) = Just f fromEntry _ = Nothing toEntry = MapFlag instance MapCRDT Set where type MapOperation_ Set = SetOp mapModify = modify fromEntry (MapSet s) = Just s fromEntry _ = Nothing toEntry = MapSet instance MapCRDT Counter where type MapOperation_ Counter = CounterOp mapModify = modify fromEntry (MapCounter s) = Just s fromEntry _ = Nothing toEntry = MapCounter instance MapCRDT Register where type MapOperation_ Register = RegisterOp mapModify = modifyRegister fromEntry (MapRegister s) = Just s fromEntry _ = Nothing toEntry = MapRegister instance MapCRDT Map where type MapOperation_ Map = MapOp mapModify = modify fromEntry (MapMap s) = Just s fromEntry _ = Nothing toEntry = MapMap -- | CRDT types class MapCRDT a => CRDT a op | a -> op, op -> a where -- | Modify a value by applying an operation modify :: op -> a -> a -- | Request riak a modification sendModify :: Connection -> BucketType -> Bucket -> Key -> [op] -> IO () instance CRDT Counter CounterOp where modify = modifyCounter sendModify = counterSendUpdate instance CRDT Set SetOp where modify = modifySet sendModify = setSendUpdate instance CRDT Map MapOp where modify = modifyMap sendModify = mapSendUpdate