Skip to content

Commit

Permalink
feat: Extend do_either API (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
moritzploss committed Jun 13, 2021
1 parent 317e490 commit afcf8b0
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -11,7 +11,7 @@ implementations for commonly used class instances.
To install the latest version of `do` from [`hex`](https://hex.pm/packages/do),
add `do` to the `deps` in your rebar config file:

{do, "1.8.1"}
{do, "1.9.0"}

### What's in the box

Expand Down
60 changes: 58 additions & 2 deletions doc/do_either.html
Expand Up @@ -48,11 +48,19 @@ <h3 class="typedecl"><a name="type-traversable">traversable()</a></h3>
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#bind-2">bind/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#do-2">do/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#either-3">either/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#errors-1">errors/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#fmap-2">fmap/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#from_error-2">from_error/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#from_ok-2">from_ok/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#is_error-1">is_error/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#is_ok-1">is_ok/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#lift-1">lift/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#liftA2-2">liftA2/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#liftm-2">liftm/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#liftmz-2">liftmz/2</a></td><td></td></tr>
<tr><td valign="top"><a href="#oks-1">oks/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#partition-1">partition/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#pure-1">pure/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#sequence-1">sequence/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#then-2">then/2</a></td><td></td></tr>
Expand All @@ -72,9 +80,45 @@ <h3 class="function"><a name="do-2">do/2</a></h3>
<p> </p>
</div>

<h3 class="function"><a name="either-3">either/3</a></h3>
<div class="spec">
<p><tt>either(F1::<a href="#type-fn">fn</a>(A, C), F2::<a href="#type-fn">fn</a>(B, C), Either::<a href="#type-either">either</a>(A, B)) -&gt; C</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="errors-1">errors/1</a></h3>
<div class="spec">
<p><tt>errors(Eithers::[<a href="#type-either">either</a>(A, term())]) -&gt; [A]</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="fmap-2">fmap/2</a></h3>
<div class="spec">
<p><tt>fmap(F::<a href="#type-fn">fn</a>(B, C), X2::<a href="#type-either">either</a>(A, B)) -&gt; <a href="#type-either">either</a>(A, C)</tt><br></p>
<p><tt>fmap(F::<a href="#type-fn">fn</a>(B, C), Either::<a href="#type-either">either</a>(A, B)) -&gt; <a href="#type-either">either</a>(A, C)</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="from_error-2">from_error/2</a></h3>
<div class="spec">
<p><tt>from_error(A, Either::<a href="#type-either">either</a>(A, term())) -&gt; A</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="from_ok-2">from_ok/2</a></h3>
<div class="spec">
<p><tt>from_ok(B, Either::<a href="#type-either">either</a>(term(), B)) -&gt; B</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="is_error-1">is_error/1</a></h3>
<div class="spec">
<p><tt>is_error(Either::<a href="#type-either">either</a>(term(), term())) -&gt; boolean()</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="is_ok-1">is_ok/1</a></h3>
<div class="spec">
<p><tt>is_ok(Either::<a href="#type-either">either</a>(term(), term())) -&gt; boolean()</tt><br></p>
<p> </p>
</div>

Expand All @@ -86,7 +130,7 @@ <h3 class="function"><a name="lift-1">lift/1</a></h3>

<h3 class="function"><a name="liftA2-2">liftA2/2</a></h3>
<div class="spec">
<p><tt>liftA2(X1::<a href="#type-either">either</a>(A1, <a href="#type-fn">fn</a>(B, C)), Either::<a href="#type-either">either</a>(A2, B)) -&gt; <a href="#type-either">either</a>(A1 | A2, C)</tt><br></p>
<p><tt>liftA2(Either::<a href="#type-either">either</a>(A1, <a href="#type-fn">fn</a>(B, C)), Either::<a href="#type-either">either</a>(A2, B)) -&gt; <a href="#type-either">either</a>(A1 | A2, C)</tt><br></p>
<p> </p>
</div>

Expand All @@ -102,6 +146,18 @@ <h3 class="function"><a name="liftmz-2">liftmz/2</a></h3>
<p> </p>
</div>

<h3 class="function"><a name="oks-1">oks/1</a></h3>
<div class="spec">
<p><tt>oks(Eithers::[<a href="#type-either">either</a>(term(), B)]) -&gt; [B]</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="partition-1">partition/1</a></h3>
<div class="spec">
<p><tt>partition(Eithers::[<a href="#type-either">either</a>(A, B)]) -&gt; {[A], [B]}</tt><br></p>
<p> </p>
</div>

<h3 class="function"><a name="pure-1">pure/1</a></h3>
<div class="spec">
<p><tt>pure(B) -&gt; <a href="#type-either">either</a>(term(), B)</tt><br></p>
Expand Down
2 changes: 1 addition & 1 deletion src/do.app.src
@@ -1,6 +1,6 @@
{application, do,
[{description, "Monads, Functors and Do-Notation for Erlang"},
{vsn, "1.8.1"},
{vsn, "1.9.0"},
{registered, []},
{applications,
[kernel,
Expand Down
104 changes: 94 additions & 10 deletions src/do_either.erl
Expand Up @@ -11,16 +11,29 @@
-behaviour(do_monad).

%%%_* Exports =================================================================
-define(API, [ bind/2,
do/2,
-define(API, [ % functor
fmap/2,
lift/1,
% applicative
liftA2/2,
liftm/2,
liftmz/2,
pure/1,
sequence/1,
then/2]).
% monad
bind/2,
do/2,
lift/1,
liftm/2,
then/2,
% either
liftmz/2,
either/3,
errors/1,
oks/1,
is_error/1,
is_ok/1,
from_error/2,
from_ok/2,
partition/1]).

-export(?API).
-ignore_xref(?API).

Expand All @@ -31,12 +44,12 @@

%%%_* Code ====================================================================
%%%_* functor -----------------------------------------------------------------
-spec fmap(fn(B, C), either(A, B)) -> either(A, C).
-spec fmap(fn(B, C), Either :: either(A, B)) -> either(A, C).
fmap(F, {error, A}) when ?isF1(F) -> {error, A};
fmap(F, {ok, B}) when ?isF1(F) -> {ok, F(B)}.

%%%_* applicative -------------------------------------------------------------
-spec liftA2(either(A1, fn(B, C)), either(A2, B)) -> either(A1 | A2, C).
-spec liftA2(Either :: either(A1, fn(B, C)), either(A2, B)) -> either(A1 | A2, C).
liftA2({ok, F}, Either) when ?isF1(F) -> fmap(F, Either);
liftA2({error, Reason}, _) -> {error, Reason}.

Expand All @@ -59,11 +72,48 @@ lift(F) -> do_monad:lift(F, ?MODULE).
-spec liftm(fun(), [either(_, B)]) -> either(_, B).
liftm(F, Eithers) -> do_monad:liftm(F, Eithers, ?MODULE).

-spec then(either(A, _), fn(either(B, C))) -> either(A | B, C).
then(Either, F) -> do_monad:then(Either, F, ?MODULE).

%%%_* either ------------------------------------------------------------------
-spec liftmz(fun(), [fn(either(_, B))]) -> either(_, B).
liftmz(F, Eithers) -> do_monad:liftmz(F, Eithers, ?MODULE).

-spec then(either(A, _), fn(either(B, C))) -> either(A | B, C).
then(Either, F) -> do_monad:then(Either, F, ?MODULE).
-spec either(fn(A, C), fn(B, C), Either :: either(A, B)) -> C.
either(F1, F2, {error, A}) when ?isF1(F1), ?isF1(F2) -> F1(A);
either(F1, F2, {ok, B}) when ?isF1(F1), ?isF1(F2) -> F2(B).

-spec errors([either(A, _)]) -> [A].
errors(Eithers) when is_list(Eithers) ->
lists:filtermap(fun({error, A}) -> {true, A};
({ok, _B}) -> false end, Eithers).

-spec oks([either(_, B)]) -> [B].
oks(Eithers) when is_list(Eithers) ->
lists:filtermap(fun({ok, B}) -> {true, B};
({error, _A}) -> false end, Eithers).


-spec is_error(Either :: either(_, _)) -> boolean().
is_error({error, _}) -> true;
is_error({ok, _}) -> false.

-spec is_ok(either(_, _)) -> boolean().
is_ok(Either) -> not is_error(Either).

-spec from_error(A, Either :: either(A, _)) -> A.
from_error(_A, {error, A}) -> A;
from_error(A, {ok, _B}) -> A.

-spec from_ok(B, Either :: either(_, B)) -> B.
from_ok(_B, {ok, B}) -> B;
from_ok(B, {error, _A}) -> B.

-spec partition([either(A, B)]) -> {[A], [B]}.
partition(Eithers) when is_list(Eithers) ->
lists:foldr(fun({error, A}, {As, Bs}) -> {[A | As], Bs};
({ok, B}, {As, Bs}) -> {As, [B | Bs]} end,
{[], []}, Eithers).

%%%_* internal ----------------------------------------------------------------
flat({ok, {error, A}}) -> {error, A};
Expand Down Expand Up @@ -120,4 +170,38 @@ sequence_test() ->
?assertEqual({ok, #{a => 1, b => 2}}, sequence(#{a => {ok, 1}, b => {ok, 2}})),
?assertEqual({error, reason}, sequence(#{a => {ok, 1}, b => {error, reason}})).

either_test() ->
F1 = fun(X) -> X end,
F2 = fun(_) -> 0 end,
?assertEqual(1, either(F2, F1, {ok, 1})),
?assertEqual(2, either(F1, F2, {error, 2})).

errors_test() ->
?assertEqual([], errors([])),
?assertEqual([1, 2], errors([{ok, 3}, {error, 1}, {error, 2}, {ok, 4}])).

oks_test() ->
?assertEqual([], oks([])),
?assertEqual([3, 4], oks([{ok, 3}, {error, 1}, {error, 2}, {ok, 4}])).

is_error_test() ->
?assertEqual(true, is_error({error, 1})),
?assertEqual(false, is_error({ok, 1})).

is_ok_test() ->
?assertEqual(false, is_ok({error, 1})),
?assertEqual(true, is_ok({ok, 1})).

from_error_test() ->
?assertEqual(1, from_error(2, {error, 1})),
?assertEqual(2, from_error(2, {ok, 1})).

from_ok_test() ->
?assertEqual(1, from_ok(2, {ok, 1})),
?assertEqual(2, from_ok(2, {error, 1})).

partition_test() ->
?assertEqual({[], []}, partition([])),
?assertEqual({[1, 2], [3, 4]}, partition([{ok, 3}, {error, 1}, {error, 2}, {ok, 4}])).

-endif.
6 changes: 3 additions & 3 deletions src/do_maybe.erl
Expand Up @@ -73,13 +73,13 @@ lift(F) -> do_monad:lift(F, ?MODULE).
-spec liftm(fun(), [maybe(A)]) -> maybe(A).
liftm(F, Maybes) -> do_monad:liftm(F, Maybes, ?MODULE).

-spec liftmz(fun(), [fn(maybe(A))]) -> maybe(A).
liftmz(F, Maybes) -> do_monad:liftmz(F, Maybes, ?MODULE).

-spec then(maybe(_), fn(maybe(A))) -> maybe(A).
then(Maybe, F) -> do_monad:then(Maybe, F, ?MODULE).

%%%_* maybe -------------------------------------------------------------------
-spec liftmz(fun(), [fn(maybe(A))]) -> maybe(A).
liftmz(F, Maybes) -> do_monad:liftmz(F, Maybes, ?MODULE).

-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).
Expand Down

0 comments on commit afcf8b0

Please sign in to comment.