Current section

Files

Jump to
do src instances do_maybe.erl
Raw

src/instances/do_maybe.erl

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% @doc The Maybe Monad.
%%% @end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%_* Module declaration ======================================================
-module(do_maybe).
-behaviour(do_semigroup).
-behaviour(do_monoid).
-behaviour(do_foldable).
-behaviour(do_functor).
-behaviour(do_applicative).
-behaviour(do_traversable).
-behaviour(do_monad).
%%%_* Exports =================================================================
-define(API, [ % semigroup
append/2,
% monoid
mempty/0,
% foldable
foldr/3,
% functor
fmap/2,
% applicative
liftA2/2,
pure/1,
% traversable
sequence/1,
traverse/2,
% monad
bind/2,
do/2,
lift/1,
liftm/2,
then/2,
% maybe
cat_maybes/1,
from_just/1,
from_maybe/2,
is_just/1,
is_nothing/1,
list_to_maybe/1,
map_maybes/2,
maybe/3,
maybe_to_list/1]).
-export(?API).
-ignore_xref(?API).
%%%_* Includes ================================================================
-include("do_internal.hrl").
-include("do_types.hrl").
-include("do.hrl").
%%%_* Code ====================================================================
%%%_* semigroup ---------------------------------------------------------------
-spec append(maybe(A), maybe(A)) -> maybe(A).
append({just, A1}, {just, A2}) -> {just, do_semigroup:append(A1, A2)};
append(nothing, {just, A}) -> {just, A};
append({just, A}, nothing) -> {just, A};
append(nothing, nothing) -> nothing.
%%%_* monoid ------------------------------------------------------------------
-spec mempty() -> maybe(_).
mempty() -> nothing.
%%%_* foldable ----------------------------------------------------------------
-spec foldr(fn(A, B, B), B, maybe(A)) -> B.
foldr(_, Acc, nothing) -> Acc;
foldr(F, Acc, {just, A}) -> F(A, Acc).
%%%_* functor -----------------------------------------------------------------
-spec fmap(fn(A, B), Maybe :: maybe(A)) -> maybe(B).
fmap(F, {just, A}) when ?isF1(F) -> {just, F(A)};
fmap(F, nothing) when ?isF1(F) -> nothing.
%%%_* applicative -------------------------------------------------------------
-spec liftA2(Maybe :: maybe(fn(A, B)), Maybe :: maybe(A)) -> maybe(B).
liftA2({just, F}, Maybe) when ?isF1(F) -> fmap(F, Maybe);
liftA2(nothing, _) -> nothing.
-spec pure(A) -> maybe(A).
pure(A) -> {just, A}.
%%%_* traversable -------------------------------------------------------------
-spec sequence(maybe(applicative(A))) -> applicative(maybe(A)).
sequence({just, A} = Maybe) -> do_traversable:sequence(Maybe, ?MODULE, ?Mod(A));
sequence(nothing) -> nothing.
-spec traverse(fn(A, applicative(B)), maybe(A)) -> applicative(maybe(B)).
traverse(F, Maybe) when ?isF1(F) -> fmap(F, Maybe).
%%%_* monad -------------------------------------------------------------------
-spec bind(maybe(A), fn(A, maybe(B))) -> maybe(B).
bind(Maybe, F) when ?isF1(F) -> flat(fmap(F, Maybe)).
-spec do(maybe(A), [fn(A, maybe(B)) | fn(maybe(B))]) -> maybe(B).
do(Maybe, Fs) -> do_monad:do(Maybe, Fs).
-spec lift(fn(A, B)) -> fn(monad(A), monad(B)).
lift(F) -> do_monad:lift(F, ?MODULE).
-spec liftm(fun(), [maybe(A)]) -> maybe(A).
liftm(F, Maybes) -> do_monad:liftm(F, Maybes).
-spec then(maybe(_), fn(maybe(A))) -> maybe(A).
then(Maybe, F) -> do_monad:then(Maybe, F).
%%%_* maybe -------------------------------------------------------------------
-spec maybe(B, fn(A, B), Maybe :: maybe(A)) -> B.
maybe(B, F, nothing) when ?isF1(F) -> B;
maybe(_, F, {just, A}) when ?isF1(F) -> F(A).
-spec is_just(Maybe :: maybe(_)) -> boolean().
is_just({just, _}) -> true;
is_just(nothing) -> false.
-spec is_nothing(maybe(_)) -> boolean().
is_nothing(A) -> not is_just(A).
-spec from_just(Maybe :: maybe(A)) -> A.
from_just({just, A}) -> A.
-spec from_maybe(A, Maybe :: maybe(A)) -> A.
from_maybe(A, nothing) -> A;
from_maybe(_, {just, V}) -> V.
-spec list_to_maybe([A]) -> maybe(A).
list_to_maybe([A | _]) -> pure(A);
list_to_maybe([]) -> nothing.
-spec maybe_to_list(Maybe :: maybe(A)) -> [A].
maybe_to_list({just, A}) -> [A];
maybe_to_list(nothing) -> [].
-spec cat_maybes([maybe(A)]) -> [A].
cat_maybes(Maybes) when is_list(Maybes) ->
lists:flatmap(fun({just, A}) -> [A];
(nothing) -> [] end, Maybes).
-spec map_maybes(fn(A, maybe(B)), [A]) -> [B].
map_maybes(F, List) when ?isF1(F), is_list(List) ->
cat_maybes(do_list:fmap(F, List)).
%%%_* internal ----------------------------------------------------------------
flat({just, nothing}) -> nothing;
flat(nothing) -> nothing;
flat({just, {just, A}}) -> {just, A}.
%%%_* Tests ===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
foldr_test() ->
F = fun(A, B) -> A + B end,
?equals(2, foldr(F, 1, {just, 1})),
?equals(1, foldr(F, 1, nothing)).
append_test() ->
?equals(mempty(), append(mempty(), mempty())),
?equals(pure(1), append(pure(1), mempty())),
?equals(pure([1, 2]), append(pure([1]), pure([2]))),
?equals(pure(1), append(mempty(), pure(1))).
traverse_test() ->
F = fun(A) -> A + 1 end,
?equals({just, 2}, traverse(F, {just, 1})).
sequence_list_test() ->
?equals([{just, 1}], sequence({just, [1]})).
sequence_test() ->
?equals(nothing, sequence(nothing)).
pure_test() ->
?assertEqual({just, {just, 3}}, pure({just, 3})),
?assertEqual({just, 3}, pure(3)).
lift_test() ->
F = fun(A) -> A + 1 end,
Lifted = lift(F),
?assertEqual({just, 2}, Lifted({just, 1})),
?assertEqual(nothing, Lifted(nothing)).
liftA2_test() ->
F = fun(A) -> A + 1 end,
?equals({just, 3}, liftA2({just, F}, {just, 2})),
?equals(nothing, liftA2({just, F}, nothing)),
?equals(nothing, liftA2(nothing, {just, 2})),
?equals(nothing, liftA2(nothing, nothing)).
liftm_test() ->
F = fun(A, B, C) -> A + B + C end,
?equals({just, 4}, liftm(F, [{just, 1}, {just, 2}, {just, 1}])),
?equals(nothing, liftm(F, [{just, 1}, nothing, {just, 1}])),
?equals(nothing, liftm(F, [nothing, {just, 2}, {just, 1}])),
?equals(nothing, liftm(F, [nothing, nothing, {just, 1}])).
bind_test() ->
Fjust = fun(A) -> {just, A + 1} end,
Fnothing = fun(_) -> nothing end,
?equals({just, 3}, bind({just, 2},Fjust)),
?equals(nothing, bind(nothing, Fjust)),
?equals(nothing, bind({just, 2}, Fnothing)),
?equals(nothing, bind(nothing, Fnothing)).
do_test() ->
Fun0 = fun() -> ?pure(5) end,
Fun = fun(A) -> ?pure(A + 1) end,
?equals({just, 4}, do({just, 3}, [Fun])),
?equals({just, 6}, do({just, 3}, [Fun0, Fun])),
?equals(nothing, do(nothing, [Fun])).
maybe_test() ->
F = fun(A) -> A + 1 end,
?assertEqual(1, maybe(1, F, nothing)),
?assertEqual(2, maybe(1, F, {just, 1})).
is_just_test() ->
?assertEqual(false, is_just(nothing)),
?assertEqual(true, is_just({just, 1})).
is_nothing_test() ->
?assertEqual(true, is_nothing(nothing)),
?assertEqual(false, is_nothing({just, 1})).
from_just_test() ->
?assertEqual(1, from_just({just, 1})),
?assertError(function_clause, from_just(nothing)).
from_maybe_test() ->
?assertEqual(2, from_maybe(1, {just, 2})),
?assertEqual(1, from_maybe(1, nothing)).
list_to_maybe_test() ->
?assertEqual({just, 1}, list_to_maybe([1])),
?assertEqual({just, 1}, list_to_maybe([1, 2])),
?assertEqual(nothing, list_to_maybe([])).
maybe_to_list_test() ->
?assertEqual([1], maybe_to_list({just, 1})),
?assertEqual([], maybe_to_list(nothing)).
cat_maybes_test() ->
?assertEqual([1], cat_maybes([{just, 1}])),
?assertEqual([], cat_maybes([nothing])),
?assertEqual([], cat_maybes([])),
?assertEqual([1, 2], cat_maybes([{just, 1}, nothing, {just, 2}])).
map_maybes_test() ->
F = fun(N) when N < 5 -> {just, N};
(_) -> nothing end,
?assertEqual([], map_maybes(F, [])),
?assertEqual([1, 2, 3, 4], map_maybes(F, [1, 2, 3, 4, 5, 6, 7])).
-endif.