Current section
Files
Jump to
Current section
Files
src/do.erl
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% @doc The Do Module.
%%% @end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%_* Module declaration ======================================================
-module(do).
%%%_* Exports =================================================================
-define(API, [ do/2,
bind/2,
then/2,
register_monad/1,
get_monads/0]).
-export(?API).
-ignore_xref(?API).
%%%_* Includes ================================================================
-include("do_types.hrl").
-include("do_macros.hrl").
-include("do.hrl").
%%%_* Macros ==================================================================
-define(MONADS, [do_either, do_list, do_maybe]).
-define(KEY, {?MODULE, monads}).
%%%_* Code ====================================================================
%%%_* API ---------------------------------------------------------------------
-spec do(monad(A), list(fn(A, monad(B)) | fn(monad(B)))) -> monad(B).
do(Monad, Funs) -> do_monad:do(Monad, Funs, get_monads()).
-spec bind(fn(A, monad(B)), monad(A)) -> monad(B).
bind(F, Monad) when ?isF1(F) -> do(Monad, [F]).
-spec then(fn(monad(B)), monad(_)) -> monad(B).
then(F, Monad) when ?isF0(F) -> do(Monad, [F]).
-spec register_monad(atom()) -> either(duplicate_monad, ok).
register_monad(Mod) when is_atom(Mod) ->
Monads = get_monads(),
case lists:member(Mod, Monads) of
true -> {error, duplicate_monad};
false -> {ok, put_monads(Monads ++ [Mod])}
end.
-spec get_monads() -> [atom()].
get_monads() ->
case persistent_term:get(?KEY, undefined) of
undefined ->
ok = put_monads(?MONADS),
?MONADS;
Monads ->
Monads
end.
%%%_* internal ----------------------------------------------------------------
put_monads(Monads) ->
ok = persistent_term:put(?KEY, Monads).
%%%_* Tests ===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
fmap_macro_test() ->
F = fun(A) -> A + 1 end,
?assertEqual({ok, 2}, ?fmap(F, {ok, 1})).
lift_macro_test() ->
F = fun(A) -> A + 1 end,
Lifted = ?lift(F),
?assertEqual({ok, 2}, Lifted({ok, 1})),
?assertEqual({error, rsn}, Lifted({error, rsn})).
sequence_macro_test() ->
?assertEqual({ok, [1, 2]}, ?sequence([{ok, 1}, {ok, 2}])).
bind_macro_test() ->
F = fun(A) -> {ok, A + 1} end,
?assertEqual({ok, 3}, ?bind(F, {ok, 2})),
?assertEqual({error, reason}, ?bind(F, {error, reason})).
then_macro_test() ->
F = fun() -> {ok, 1} end,
?assertEqual({ok, 1}, ?then(F, {ok, 2})),
?assertEqual({error, reason}, ?then(F, {error, reason})).
do_macro_test() ->
Fun0 = fun() -> ?pure(3) end,
Fun = fun(A) -> ?pure(A + 1) end,
?assertEqual({ok, 4}, ?do({ok, 3}, [Fun0, Fun])),
?assertEqual({error, reason}, ?do({error, reason}, [Fun])),
?assertEqual(error, ?do(error, [Fun])),
?assertError({error, {no_monad, foo}}, ?do(foo, [Fun])),
?assertEqual([2], ?do([1], [Fun])).
liftm_macro_test() ->
F = fun(A, B, C) -> A + B + C end,
?assertEqual({ok, 6}, ?liftm(F, {ok, 1}, {ok, 2}, {ok, 3})),
?assertError(function_clause, ?liftm(F, {ok, 1}, {ok, 2}, ?thunk({ok, 3}))),
?assertEqual({ok, 6}, ?liftm(F, {ok, 1}, {ok, 2}, {ok, 3})),
?assertEqual({error, 3}, ?liftm(F, {ok, 1}, {ok, 2}, {error, 3})).
liftA2_macro_test() ->
F = fun(A) -> A + 1 end,
?assertEqual({ok, 3}, ?liftA2({ok, F}, {ok, 2})),
?assertEqual({error, 3}, ?liftA2({ok, F}, {error, 3})),
?assertEqual({error, 1}, ?liftA2({error, 1}, {ok, 3})).
register_monad_test() ->
?assertNot(lists:member(test, get_monads())),
?assertMatch({ok, _}, register_monad(test)),
?assert(lists:member(test, get_monads())),
?assertMatch({error, _}, register_monad(test)).
-endif.