Current section

Files

Jump to
fifo_dt src ft_obj.erl
Raw

src/ft_obj.erl

%% @doc A suite of functions that operate on the algebraic data type
%% `ft_obj'.
%%
%% TODO Possibly move type/record defs in there and use accessor funs
%% and opaque types.
%%
%% Taken form https://github.com/Licenser/try-try-try/blob/master/2011/
%% riak-core-conflict-resolution/rts/src/rts_obj.erl
-module(ft_obj).
-export([ancestors/1, children/1, equal/1, equal/2, merge/2, unique/1,
update/3, update/1, is_a/1, new/0, new/2, needs_update/1]).
-export([val/1, vclock/1]).
-ignore_xref([
ancestors/1,
equal/1,
unique/1,
vclock/1
]).
-type obj_val() :: term().
-record(sniffle_obj, {val :: obj_val(),
vclock :: vclock:vclock()}).
-record(snarl_obj, {val :: obj_val(),
vclock :: vclock:vclock()}).
-record(ft_obj, {val :: obj_val(),
vclock = vclock:fresh() :: vclock:vclock()}).
-type obj() :: #ft_obj{} | not_found.
-type any_obj() :: #snarl_obj{} |
#sniffle_obj{} | obj().
-export_type([any_obj/0, obj/0]).
-callback reconcile([term()]) ->
term().
-spec new() -> obj().
new() ->
#ft_obj{}.
-spec new(Value::obj_val(), Coordinator::atom()) -> obj().
new(Value, Coordinator) ->
update(Value, Coordinator, new()).
%% @pure
%%
%% @doc Given a list of `obj()' return a list of all the
%% ancestors. Ancestors are objects that all the other objects in the
%% list have descent from.
-spec ancestors([any_obj()]) -> [obj()].
ancestors(Objs0) ->
Objs = [update(O) || O <- Objs0, O /= not_found],
As = [[O2 || O2 <- Objs,
ancestor(O2#ft_obj.vclock,
O1#ft_obj.vclock)] || O1 <- Objs],
unique(lists:flatten(As)).
%% @pure
%%
%% @doc Predicate to determine if `Va' is ancestor of `Vb'.
-spec ancestor(vclock:vclock(), vclock:vclock()) -> boolean().
ancestor(Va, Vb) ->
vclock:descends(Vb, Va) andalso (vclock:descends(Va, Vb) == false).
%% @pure
%%
%% @doc Given a list of `obj()' return a list of the children
%% objects. Children are the descendants of all others objects.
-spec children([any_obj() | obj()]) -> [obj()].
children(ObjsIn) ->
Objs = [update(O) || O <- ObjsIn],
unique(Objs) -- ancestors(Objs).
%% @pure
%%
%% @doc Predeicate to determine if `ObjA' and `ObjB' are equal.
-spec equal(ObjA::any_obj() | obj(), ObjB::any_obj() | obj()) -> boolean().
equal(A, B) ->
equal1(update(A), update(B)).
-spec equal1(ObjA::obj(), ObjB::obj()) -> boolean().
equal1(#ft_obj{vclock=A}, #ft_obj{vclock=B}) -> vclock:equal(A, B);
equal1(not_found, not_found) -> true;
equal1(_, _) -> false.
%% @pure
%%
%% @doc Closure around `equal/2' for use with HOFs (damn verbose
%% Erlang).
-spec equal(ObjA::any_obj() | obj()) ->
fun((ObjB::any_obj() | obj()) ->boolean()).
equal(ObjA) ->
ObjA1 = update(ObjA),
fun(ObjB) -> equal1(ObjA1, update(ObjB)) end.
%% @pure
%%
%% @doc Merge the list of `Objs', calling the appropriate reconcile
%% fun if there are siblings.
-spec merge(atom(), [any_obj() | obj()]) -> obj().
merge(FSM, Objs) ->
merge1(FSM, [update(O) || O <- Objs]).
-spec merge1(atom(), [obj()]) -> obj().
merge1(FSM, [not_found|_]=Objs) ->
P = fun(X) -> X == not_found end,
case lists:all(P, Objs) of
true -> not_found;
false -> merge1(FSM, lists:dropwhile(P, Objs))
end;
merge1(FSM, [#ft_obj{}|_]=Objs) ->
case children(Objs) of
[] -> not_found;
[Child] -> Child;
Chldrn ->
Val = FSM:reconcile(lists:map(fun val1/1, Chldrn)),
MergedVC = vclock:merge(lists:map(fun vclock1/1, Chldrn)),
make(Val, MergedVC)
end.
%% @pure
%%
%% @doc Given a list of `Objs' return the list of uniques.
-spec unique([any_obj()]) -> [obj()].
unique(Objs) ->
F = fun(not_found, Acc) ->
Acc;
(Obj, Acc) ->
Obj1 = update(Obj),
case lists:any(equal(Obj1), Acc) of
true -> Acc;
false -> [Obj1|Acc]
end
end,
lists:foldl(F, [], Objs).
%% @pure
%%
%% @doc Given a `Val' update the `Obj'. The `Updater' is the name of
%% the entity performing the update.
-spec update(obj_val(), node(), any_obj() | obj()) -> obj().
update(Val, Updater, O) ->
update1(Val, Updater, update(O)).
update1(Val, Updater, #ft_obj{vclock=VClock0}) ->
VClock = vclock:increment(Updater, VClock0),
make(Val, VClock).
-spec val(any_obj() | obj()) -> any().
val(O) -> val1(update(O)).
-spec val1(obj()) -> any().
val1(#ft_obj{val=Val}) -> Val;
val1(not_found) -> not_found.
%% @pure
%%
%% @doc Given a vclock type `Obj' retrieve the vclock.
-spec vclock(any_obj() | obj()) -> vclock:vclock().
vclock(O) -> vclock1(update(O)).
-spec vclock1(obj()) -> vclock:vclock().
vclock1(#ft_obj{vclock=VC}) -> VC.
-spec update(any_obj() | obj()) -> obj().
update(#ft_obj{} = O) ->
O;
update(#snarl_obj{val = V, vclock = C}) ->
make(V, C);
update(#sniffle_obj{val = V, vclock = C}) ->
make(V, C);
update(not_found) ->
not_found.
-spec is_a(any_obj() | obj()) -> boolean().
is_a(#ft_obj{}) ->
true;
is_a(#snarl_obj{}) ->
true;
is_a(#sniffle_obj{}) ->
true;
is_a(_) ->
false.
-spec needs_update(any_obj() | obj()) -> boolean().
needs_update(#ft_obj{}) ->
false;
needs_update(#snarl_obj{}) ->
true;
needs_update(#sniffle_obj{}) ->
true.
make(V, C) ->
#ft_obj{val = V, vclock = C}.