Skip to content

Vordict (take a look, don't merge ;) #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
This project is some experimentation with CRDT's in Erlang, please be aware that it's an **experiment** meaning not suitable for production use, not optimised for either speed or space and probably broken in some places.

The code is written to look nice, be understandable not to make the most out of every operation.

Consider yourself warned, now go have some fun with it!

## Naming conventions

The first letter of the module name indicates what kind of CRDT the mudle implements:

* m* - implements CmRDT's (message based)
* v* - implements CvRDT's (state based)


## Resources
* [Call me maybe: Riak](http://aphyr.com/posts/285-call-me-maybe-riak)
* [Marc Shapiro](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/pubs.html#SEC-SSS)
* [CRDTs: Consistency without concurrency control](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/RR-6956.pdf)
* [An Optimized Conflict-free Replicated Set](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/RR-8083.pdf)
* [Conflict-free Replicated Data Types](http://hal.inria.fr/docs/00/61/73/41/PDF/RR-7687.pdf)
This project is some experimentation with CRDT's in Erlang, please be aware that it's an **experiment** meaning not suitable for production use, not optimised for either speed or space and probably broken in some places.

The code is written to look nice, be understandable not to make the most out of every operation.

Consider yourself warned, now go have some fun with it!

## Naming conventions

The first letter of the module name indicates what kind of CRDT the mudle implements:

* m* - implements CmRDT's (message based)
* v* - implements CvRDT's (state based)


## Resources

* [Erlounge Presentation](http://www.slideshare.net/Licenser/crd-ts-for-engineers)
* [Call me maybe: Riak](http://aphyr.com/posts/285-call-me-maybe-riak)
* [Marc Shapiro](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/pubs.html#SEC-SSS)
* [CRDTs: Consistency without concurrency control](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/RR-6956.pdf)
* [An Optimized Conflict-free Replicated Set](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/RR-8083.pdf)
* [Conflict-free Replicated Data Types](http://hal.inria.fr/docs/00/61/73/41/PDF/RR-7687.pdf)
* [A comprehensive study of
Convergent and Commutative Replicated Data Types](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/Comprehensive-CRDTs-RR7506-2011-01.pdf)
* [Designing a commutative replicated data type](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/Commutative-Replicated-Data-Type-RR-6320_2007-10.pdf)
* [A commutative replicated data type for cooperative editing](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/icdcs09-treedoc.pdf)
Convergent and Commutative Replicated Data Types](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/Comprehensive-CRDTs-RR7506-2011-01.pdf)
* [Designing a commutative replicated data type](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/Commutative-Replicated-Data-Type-RR-6320_2007-10.pdf)
* [A commutative replicated data type for cooperative editing](http://pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/icdcs09-treedoc.pdf)
126 changes: 60 additions & 66 deletions src/vordict.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
-include_lib("eunit/include/eunit.hrl").
-endif.

-export([new/0, add/2, add/3, remove/2, merge/2, value/1, from_list/1, gc/1]).
-export([new/0, store/3, remove/2, merge/2, value/1]). %from_list/1, gc/1 ]).

-record(vorset, {adds :: vgset:vgset(),
-record(vordict, {stores :: orddict:orddict(),
removes :: vgset:vgset()}).

-opaque vorset() :: #vorset{}.
-opaque vordict() :: #vordict{}.

-export_type([vorset/0]).
-export_type([vordict/0]).

%%%===================================================================
%%% Implementation
Expand All @@ -23,67 +23,58 @@
%% Creates a new empty OR Set.
%% @end
%%--------------------------------------------------------------------
-spec new() -> vorset().
-spec new() -> vordict().
new() ->
#vorset{adds = vgset:new(),
removes = vgset:new()}.
#vordict{stores = orddict:new(),
removes = vgset:new()}.

%%--------------------------------------------------------------------
%% @doc
%% Creates a new OR Set form a list by adding each element in the
%% list.
%% @end
%%--------------------------------------------------------------------
-spec from_list(list()) -> vorset().
from_list(L) ->
ID = ecrdt:id(),
#vorset{adds = vgset:from_list([ {E, ID} || E <- L]),
removes = vgset:new()}.

%%--------------------------------------------------------------------
%% @doc
%% Adds an element to the OR set with a given master ID.
%% @end
%%--------------------------------------------------------------------
-spec add(ID::term(), Element::term(), ORSet::vorset()) -> ORSet1::vorset().
add(ID, Element, ORSet = #vorset{adds = Adds}) ->
ORSet#vorset{adds = vgset:add({Element, ID}, Adds)}.
% -spec from_list(list()) -> vorset().
% from_list(L) ->
% ID = ecrdt:id(),
% #vorset{adds = vgset:from_list([ {E, ID} || E <- L]),
% removes = vgset:new()}.

%%--------------------------------------------------------------------
%% @doc
%% Adds an element to the OR set using the default ID function
%% provided by ecrdt:id().
%% @end
%%--------------------------------------------------------------------
-spec add(Element::term(), ORSet::vorset()) -> ORSet1::vorset().
add(Element, ORSet = #vorset{adds = Adds}) ->
ORSet#vorset{adds = vgset:add({Element, ecrdt:id()}, Adds)}.
-spec store(Key::term(), Value::term(), ORDict::vordict()) -> ORDict1::vordict().
store(Key, Value, ORDict = #vordict{stores = Stores}) ->
ORDict#vordict{stores = orddict:update(Key, fun(Value1) ->
ecrdt:merge(Value1, vlwwregister:new(Value))
end, vlwwregister:new(Value), Stores)}.

%%--------------------------------------------------------------------
%% @doc
%% Removes a element from the OR set by finding all observed adds and
%% putting them in the list of removed items.
%% @end
%%--------------------------------------------------------------------
-spec remove(Element::term(), ORSet::vorset()) -> ORSet1::vorset().
remove(Element, ORSet = #vorset{removes = Removes}) ->
CurrentExisting = [Elem || Elem = {E1, _} <- raw_value(ORSet), E1 =:= Element],
Removes1 = lists:foldl(fun(R, Rs) ->
vgset:add(R, Rs)
end, Removes, CurrentExisting),
ORSet#vorset{removes = Removes1}.
-spec remove(Key::term(), ORDict::vordict()) -> ORDict1::vordict().
remove(Key, ORDict = #vordict{removes = Removes}) ->
ORDict#vordict{removes = vgset:add(Key, Removes)}.

%%--------------------------------------------------------------------
%% @doc
%% Merges two OR Sets by taking the union of adds and removes.
%% @end
%%--------------------------------------------------------------------
-spec merge(ORSet0::vorset(), ORSet1::vorset()) -> ORSetM::vorset().
merge(#vorset{adds = Adds0,
-spec merge(ORDict0::vordict(), ORDict1::vordict()) -> ORDictM::vordict().
merge(#vordict{stores = Stores0,
removes = Removes0},
#vorset{adds = Adds1,
#vordict{stores = Stores1,
removes = Removes1}) ->
#vorset{adds = vgset:merge(Adds0, Adds1),
#vordict{stores = orddict:merge(fun(_Key, Value1, Value2) ->
ecrdt:merge(Value1, Value2)
end, Stores0, Stores1),
removes = vgset:merge(Removes0, Removes1)}.

%%--------------------------------------------------------------------
Expand All @@ -92,9 +83,9 @@ merge(#vorset{adds = Adds0,
%% substract of adds and removes then eleminating the ID field.
%% @end
%%--------------------------------------------------------------------
-spec value(ORSet::vorset()) -> [Element::term()].
value(ORSet) ->
ordsets:from_list([E || {E, _} <- raw_value(ORSet)]).
-spec value(ORDict::vordict()) -> [Element::term()].
value(ORDict) ->
ordsets:from_list([ecrdt:value(E) || E <- raw_value(ORDict)]).

%%--------------------------------------------------------------------
%% @doc
Expand All @@ -105,53 +96,56 @@ value(ORSet) ->
%% lead to unexpected results!
%% @end
%%--------------------------------------------------------------------
-spec gc(ORSet::vorset()) -> ORSetGCed::vorset().
gc(ORSet) ->
#vorset{adds = vgset:from_list(raw_value(ORSet)),
removes = vgset:new()}.
% -spec gc(ORSet::vorset()) -> ORSetGCed::vorset().
% gc(ORSet) ->
% #vorset{adds = vgset:from_list(raw_value(ORSet)),
% removes = vgset:new()}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

-spec raw_value(ORSet::vorset()) -> ordsets:ordset().
raw_value(#vorset{adds = Adds,
-spec raw_value(ORDict::vordict()) -> [Element::vlwwregister:vlwwregister()].
raw_value(#vordict{stores = Stores,
removes = Removes}) ->
ordsets:subtract(vgset:value(Adds), vgset:value(Removes)).
RemovesRaw = vgset:value(Removes),
orddict:filter(fun(K, _V) ->
not lists:member(K, RemovesRaw)
end, Stores).

%%%===================================================================
%%% Tests
%%%===================================================================

-ifdef(TEST).

op(a, add, E, C1, C2, Check) ->
ID = ecrdt:id(),
{add(ID, E, C1), C2, add(ID, E, Check)};
op(b, add, E, C1, C2, Check) ->
ID = ecrdt:id(),
{C1, add(ID, E, C2), add(ID, E, Check)};
op(ab, add, E, C1, C2, Check) ->
ID = ecrdt:id(),
{add(ID, E, C1), add(ID, E, C2), add(ID, E, Check)};
op(a, remove, E, C1, C2, Check) ->
{remove(E, C1), C2, remove(E, Check)};
op(b, remove, E, C1, C2, Check) ->
{C1, remove(E, C2), remove(E, Check)};
op(ab, remove, E, C1, C2, Check) ->
{remove(E, C1), remove(E, C2), remove(E, Check)}. %;

%% Applies the list of opperaitons to three empty sets.
op(a, store, K, V, C1, C2, Check) ->
_ID = ecrdt:id(),
{store(K, V, C1), C2, store(K, V, Check)};
op(b, store, K, V, C1, C2, Check) ->
_ID = ecrdt:id(),
{C1, store(K, V, C2), store(K, V, Check)};
op(ab, store, K, V, C1, C2, Check) ->
_ID = ecrdt:id(),
{store(K, V, C1), store(K, V, C2), store(K, V, Check)};
op(a, remove, K, _V, C1, C2, Check) ->
{remove(K, C1), C2, remove(K, Check)};
op(b, remove, K, _V, C1, C2, Check) ->
{C1, remove(K, C2), remove(K, Check)};
op(ab, remove, K, _V, C1, C2, Check) ->
{remove(K, C1), remove(K, C2), remove(K, Check)}. %;

%% Applies the list of opperaitons to three empty dicts.
apply_ops(Ops) ->
lists:foldl(fun({T, O, E}, {A, B, C}) ->
op(T, O, E, A, B, C)
lists:foldl(fun({T, O, K, V}, {A, B, C}) ->
op(T, O, K, V, A, B, C)
end, {new(), new(), new()}, Ops).

%% A list of opperations and targets.
targets() ->
list({oneof([a, b, ab]), oneof([add, remove]), pos_integer()}).
list({oneof([a, b, ab]), oneof([store, remove]), atom(), pos_integer()}).

prop_vorset() ->
prop_vordict() ->
?FORALL(Ts, targets(),
begin
{A, B, C} = apply_ops(Ts),
Expand Down