Skip to content
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

feat: Extend do_either API #25

Merged
merged 1 commit into from
Jun 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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