From afcf8b0c1271909a9c46e2c197d1afe0b8e61b48 Mon Sep 17 00:00:00 2001 From: Moritz Ploss Date: Sun, 13 Jun 2021 20:53:39 +0200 Subject: [PATCH] feat: Extend do_either API (#25) --- README.md | 2 +- doc/do_either.html | 60 +++++++++++++++++++++++++- src/do.app.src | 2 +- src/do_either.erl | 104 ++++++++++++++++++++++++++++++++++++++++----- src/do_maybe.erl | 6 +-- 5 files changed, 157 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4197415..26bad6e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/doc/do_either.html b/doc/do_either.html index 07ad5a5..097e899 100644 --- a/doc/do_either.html +++ b/doc/do_either.html @@ -48,11 +48,19 @@

traversable()

Function Index

+ + + + + + + + @@ -72,9 +80,45 @@

do/2

+

either/3

+
+

either(F1::fn(A, C), F2::fn(B, C), Either::either(A, B)) -> C

+

+
+ +

errors/1

+
+

errors(Eithers::[either(A, term())]) -> [A]

+

+
+

fmap/2

-

fmap(F::fn(B, C), X2::either(A, B)) -> either(A, C)

+

fmap(F::fn(B, C), Either::either(A, B)) -> either(A, C)

+

+
+ +

from_error/2

+
+

from_error(A, Either::either(A, term())) -> A

+

+
+ +

from_ok/2

+
+

from_ok(B, Either::either(term(), B)) -> B

+

+
+ +

is_error/1

+
+

is_error(Either::either(term(), term())) -> boolean()

+

+
+ +

is_ok/1

+
+

is_ok(Either::either(term(), term())) -> boolean()

@@ -86,7 +130,7 @@

lift/1

liftA2/2

-

liftA2(X1::either(A1, fn(B, C)), Either::either(A2, B)) -> either(A1 | A2, C)

+

liftA2(Either::either(A1, fn(B, C)), Either::either(A2, B)) -> either(A1 | A2, C)

@@ -102,6 +146,18 @@

liftmz/2

+

oks/1

+
+

oks(Eithers::[either(term(), B)]) -> [B]

+

+
+ +

partition/1

+
+

partition(Eithers::[either(A, B)]) -> {[A], [B]}

+

+
+

pure/1

pure(B) -> either(term(), B)

diff --git a/src/do.app.src b/src/do.app.src index 773edf5..8b5adc6 100644 --- a/src/do.app.src +++ b/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, diff --git a/src/do_either.erl b/src/do_either.erl index 09fe6ca..03bb545 100644 --- a/src/do_either.erl +++ b/src/do_either.erl @@ -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). @@ -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}. @@ -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}; @@ -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. diff --git a/src/do_maybe.erl b/src/do_maybe.erl index 331577c..4f69fb2 100644 --- a/src/do_maybe.erl +++ b/src/do_maybe.erl @@ -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).
bind/2
do/2
either/3
errors/1
fmap/2
from_error/2
from_ok/2
is_error/1
is_ok/1
lift/1
liftA2/2
liftm/2
liftmz/2
oks/1
partition/1
pure/1
sequence/1
then/2