module Data.VectorClock.Simple (
VectorClock,
empty, singleton, fromList,
null, size, member, lookup, toList,
insert, inc, incWithDefault,
delete,
combine, max, diff,
Relation(..), relation, causes,
valid
) where
import Prelude hiding ( null, lookup, max )
import qualified Prelude
import Control.Applicative ( (<$>) )
import Data.Binary ( Binary(..) )
import Data.Data ( Data, Typeable )
import Data.Foldable ( Foldable(foldMap), foldl' )
import Data.Traversable ( Traversable(..) )
import Data.List ( sort, nub )
import Data.Maybe ( isJust, catMaybes )
import GHC.Generics ( Generic )
data VectorClock a b = VectorClock { clock :: [(a, b)] }
deriving ( Eq, Data, Generic, Typeable )
instance (Show a, Show b) => Show (VectorClock a b) where
show = show . clock
instance (Binary a, Binary b) => Binary (VectorClock a b) where
put = put . clock
get = get >>= \xys -> return (VectorClock { clock = xys })
instance Foldable (VectorClock a) where
foldMap f = foldMap f . map snd . clock
instance Functor (VectorClock a) where
fmap f vc = vc { clock = map (\(x, y) -> (x, f y)) (clock vc) }
instance Traversable (VectorClock a) where
traverse f vc =
let f' (x, y) = (x,) <$> f y in
(\xys -> vc { clock = xys }) <$> traverse f' (clock vc)
data Relation = Causes | CausedBy | Concurrent
deriving (Eq, Show)
empty :: VectorClock a b
empty = VectorClock { clock = [] }
singleton :: (Ord a) => a -> b -> VectorClock a b
singleton x y = fromList [(x, y)]
fromList :: (Ord a) => [(a, b)] -> VectorClock a b
fromList = foldl' (\vc (x, y) -> insert x y vc) empty
toList :: VectorClock a b -> [(a, b)]
toList = clock
null :: VectorClock a b -> Bool
null = Prelude.null . clock
size :: VectorClock a b -> Int
size = length . clock
extract :: (Ord a) => a -> VectorClock a b -> (Maybe b, VectorClock a b)
extract x vc =
case span (\(x', _) -> x' < x) (clock vc) of
(_, []) ->
(Nothing, vc)
(xys, xys'@((x', y') : xys'')) ->
if x' == x
then (return y', vc { clock = xys ++ xys'' })
else (Nothing, vc { clock = xys ++ xys' })
lookup :: (Ord a) => a -> VectorClock a b -> Maybe b
lookup x = fst . extract x
member :: (Ord a) => a -> VectorClock a b -> Bool
member x = isJust . lookup x
delete :: (Ord a) => a -> VectorClock a b -> VectorClock a b
delete x = snd . extract x
insert :: (Ord a) => a -> b -> VectorClock a b -> VectorClock a b
insert x y vc = vc { clock = go (clock vc) }
where
go [] = [(x, y)]
go (xy@(x', _) : xys)
| x' < x = xy : go xys
| x' == x = (x, y) : xys
| otherwise = (x, y) : xy : xys
inc :: (Ord a, Num b) => a -> VectorClock a b -> Maybe (VectorClock a b)
inc x vc = lookup x vc >>= \y -> return (insert x (y + fromInteger 1) vc)
incWithDefault :: (Ord a, Num b)
=> a
-> VectorClock a b
-> b
-> VectorClock a b
incWithDefault x vc y' =
case lookup x vc of
Nothing -> insert x (y' + fromInteger 1) vc
Just y -> insert x (y + fromInteger 1) vc
combine :: (Ord a, Ord b)
=> (a -> Maybe b -> Maybe b -> Maybe b)
-> VectorClock a b
-> VectorClock a b
-> VectorClock a b
combine f vc1 vc2 =
VectorClock { clock = catMaybes (go (clock vc1) (clock vc2)) }
where
go [] xys = map (\(x, y) -> (x ~^ f x Nothing (Just y))) xys
go xys [] = map (\(x, y) -> (x ~^ f x (Just y) Nothing)) xys
go (xy@(x, y) : xys) (xy'@(x', y') : xys')
| x < x' = (x ~^ f x (Just y) Nothing) : go xys (xy' : xys')
| x == x' = (x ~^ f x (Just y) (Just y')) : go xys xys'
| otherwise = (x' ~^ f x' Nothing (Just y')) : go (xy : xys) xys'
(~^) x v = v >>= return . (x,)
max :: (Ord a, Ord b) => VectorClock a b -> VectorClock a b -> VectorClock a b
max = combine maxEntry
where
maxEntry _ Nothing Nothing = Nothing
maxEntry _ x@(Just _) Nothing = x
maxEntry _ Nothing y@(Just _) = y
maxEntry _ (Just x) (Just y) = Just (Prelude.max x y)
relation :: (Ord a, Ord b) => VectorClock a b -> VectorClock a b -> Relation
relation vc1 vc2 = go (clock vc1) (clock vc2)
where
go xys@((x, y) : xyt) xys'@((x', y') : xyt')
| x == x' =
if y == y'
then go xyt xyt'
else if y < y'
then if checkCauses xyt xyt' then Causes else Concurrent
else if checkCauses xyt' xyt then CausedBy else Concurrent
| x < x' = if checkCauses xys' xyt then CausedBy else Concurrent
| x > x' = if checkCauses xys xyt' then Causes else Concurrent
| otherwise = Concurrent
go [] _ = Causes
go _ [] = CausedBy
checkCauses xys@((x, y) : xyt) ((x', y') : xyt')
| x == x' = if y <= y' then checkCauses xyt xyt' else False
| x < x' = False
| otherwise = checkCauses xys xyt'
checkCauses [] _ = True
checkCauses _ _ = False
causes :: (Ord a, Ord b) => VectorClock a b -> VectorClock a b -> Bool
causes vc1 vc2 = relation vc1 vc2 == Causes
diff :: (Ord a, Ord b)
=> VectorClock a b -> VectorClock a b -> Maybe (VectorClock a b)
diff vc1 vc2 =
if vc1 == vc2 then Just (fromList []) else
if vc2 `causes` vc1 then Just (combine diffOne vc1 vc2) else Nothing
where
diffOne _ Nothing Nothing = Nothing
diffOne _ x Nothing = x
diffOne _ (Just x) (Just y) = if x == y then Nothing else Just x
diffOne _ Nothing (Just _) = error "diff broken"
valid :: (Ord a, Ord b) => VectorClock a b -> Bool
valid vc = let xys = clock vc
xysSorted = sort xys
xysNub = nub xys
in xys == xysSorted && xys == xysNub