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

lefts / rights / partitionEithers / bimap methods #57

Merged
merged 3 commits into from Oct 8, 2022
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
64 changes: 64 additions & 0 deletions lib/src/either.dart
Expand Up @@ -90,6 +90,16 @@ abstract class Either<L, R> extends HKT2<_EitherHKT, L, R>
@override
Either<L, C> map<C>(C Function(R a) f);

/// Define two functions to change both the [Left] and [Right] value of the
/// [Either].
///
/// {@template fpdart_bimap_either}
/// Same as `map`+`mapLeft` but for both [Left] and [Right]
/// (`map` is only to change [Right], while `mapLeft` is only to change [Left]).
/// {@endtemplate}
Either<C, D> bimap<C, D>(C Function(L l) mLeft, D Function(R b) mRight) =>
mapLeft(mLeft).map(mRight);

/// Return a [Right] containing the value `c`.
@override
Either<L, C> pure<C>(C c) => Right<L, C>(c);
Expand Down Expand Up @@ -293,6 +303,60 @@ abstract class Either<L, R> extends HKT2<_EitherHKT, L, R>
) =>
traverseList(list, identity);

/// {@template fpdart_rights_either}
/// Extract all the [Right] values from a `List<Either<E, A>>`.
/// {@endtemplate}
static List<A> rights<E, A>(List<Either<E, A>> list) {
final resultList = <A>[];
for (var i = 0; i < list.length; i++) {
final e = list[i];
if (e is Right<E, A>) {
resultList.add(e._value);
}
}

return resultList;
}

/// {@template fpdart_lefts_either}
/// Extract all the [Left] values from a `List<Either<E, A>>`.
/// {@endtemplate}
static List<E> lefts<E, A>(List<Either<E, A>> list) {
final resultList = <E>[];
for (var i = 0; i < list.length; i++) {
final e = list[i];
if (e is Left<E, A>) {
resultList.add(e._value);
}
}

return resultList;
}

/// {@template fpdart_partition_eithers_either}
/// Extract all the [Left] and [Right] values from a `List<Either<E, A>>` and
/// return them in two partitioned [List] inside [Tuple2].
/// {@endtemplate}
static Tuple2<List<E>, List<A>> partitionEithers<E, A>(
List<Either<E, A>> list) {
final resultListLefts = <E>[];
final resultListRights = <A>[];
for (var i = 0; i < list.length; i++) {
final e = list[i];
if (e is Left<E, A>) {
resultListLefts.add(e._value);
} else if (e is Right<E, A>) {
resultListRights.add(e._value);
} else {
throw Exception(
"[fpdart]: Error when mapping Either, it should be either Left or Right.",
);
}
}

return Tuple2(resultListLefts, resultListRights);
}

/// Flat a [Either] contained inside another [Either] to be a single [Either].
factory Either.flatten(Either<L, Either<L, R>> e) => e.flatMap(identity);

Expand Down
12 changes: 12 additions & 0 deletions lib/src/io_either.dart
Expand Up @@ -90,6 +90,18 @@ class IOEither<L, R> extends HKT2<_IOEitherHKT, L, R>
@override
IOEither<L, C> map<C>(C Function(R r) f) => ap(pure(f));

/// Change the value in the [Left] of [IOEither].
IOEither<C, R> mapLeft<C>(C Function(L l) f) => IOEither(
() => (run()).match((l) => Either.left(f(l)), Either.of),
);

/// Define two functions to change both the [Left] and [Right] value of the
/// [IOEither].
///
/// {@macro fpdart_bimap_either}
IOEither<C, D> bimap<C, D>(C Function(L a) mLeft, D Function(R b) mRight) =>
mapLeft(mLeft).map(mRight);

/// Apply the function contained inside `a` to change the value on the [Right] from
/// type `R` to a value of type `C`.
@override
Expand Down
16 changes: 13 additions & 3 deletions lib/src/list_extension.dart
Expand Up @@ -502,9 +502,19 @@ extension FpdartSequenceIterableTask<T> on Iterable<Task<T>> {
Task<List<T>> sequenceTaskSeq() => Task.sequenceListSeq(toList());
}

extension FpdartSequenceIterableEither<E, T> on Iterable<Either<E, T>> {
/// {@macro fpdart_sequence__io}
Either<E, List<T>> sequenceEither() => Either.sequenceList(toList());
extension FpdartSequenceIterableEither<E, A> on Iterable<Either<E, A>> {
/// {@macro fpdart_sequence_list_either}
Either<E, List<A>> sequenceEither() => Either.sequenceList(toList());

/// {@macro fpdart_rights_either}
List<A> rightsEither() => Either.rights(toList());

/// {@macro fpdart_lefts_either}
List<E> leftsEither() => Either.lefts(toList());

/// {@macro fpdart_partition_eithers_either}
Tuple2<List<E>, List<A>> partitionEithersEither() =>
Either.partitionEithers(toList());
}

extension FpdartSequenceIterableTaskEither<E, T> on Iterable<TaskEither<E, T>> {
Expand Down
13 changes: 6 additions & 7 deletions lib/src/task_either.dart
Expand Up @@ -71,17 +71,16 @@ class TaskEither<L, R> extends HKT2<_TaskEitherHKT, L, R>
TaskEither<L, C> map<C>(C Function(R r) f) => ap(pure(f));

/// Change the value in the [Left] of [TaskEither].
TaskEither<C, R> mapLeft<C>(C Function(L l) f) => TaskEither(() async =>
(await run()).match((l) => Either.left(f(l)), (r) => Either.of(r)));
TaskEither<C, R> mapLeft<C>(C Function(L l) f) => TaskEither(
() async => (await run()).match((l) => Either.left(f(l)), Either.of),
);

/// Define two functions to change both the [Left] and [Right] value of the
/// [TaskEither].
///
/// Same as `map`+`mapLeft` but for both [Left] and [Right]
/// (`map` is only to change [Right], while `mapLeft` is only to change [Left]).
TaskEither<C, D> bimap<C, D>(
C Function(L l) mapLeft, D Function(R r) mapRight) =>
map(mapRight).mapLeft(mapLeft);
/// {@macro fpdart_bimap_either}
TaskEither<C, D> bimap<C, D>(C Function(L l) mLeft, D Function(R r) mRight) =>
mapLeft(mLeft).map(mRight);

/// Apply the function contained inside `a` to change the value on the [Right] from
/// type `R` to a value of type `C`.
Expand Down
8 changes: 8 additions & 0 deletions lib/src/tuple.dart
Expand Up @@ -60,6 +60,14 @@ class Tuple2<T1, T2> extends HKT2<_Tuple2HKT, T1, T2>
@override
Tuple2<T1, C> map<C>(C Function(T2 a) f) => mapSecond(f);

/// Change type of both values of the [Tuple].
///
/// This is the same as `mapFirst` followed by `mapSecond`.
///
/// Same as `mapBoth` but using two distinct functions instead of just one.
Tuple2<C, D> bimap<C, D>(C Function(T1 a) mFirst, D Function(T2 b) mSecond) =>
mapFirst(mFirst).mapSecond(mSecond);

/// Convert the second value of the [Tuple2] from `T2` to `Z` using `f`.
@override
Tuple2<T1, Z> extend<Z>(Z Function(Tuple2<T1, T2> t) f) =>
Expand Down
2 changes: 1 addition & 1 deletion lib/src/typeclass/functor.dart
Expand Up @@ -9,5 +9,5 @@ mixin Functor<G, A> on HKT<G, A> {
}

mixin Functor2<G, A, B> on HKT2<G, A, B> {
HKT2<G, A, C> map<C>(C Function(B a) f);
HKT2<G, A, C> map<C>(C Function(B b) f);
}
53 changes: 53 additions & 0 deletions test/src/either_test.dart
Expand Up @@ -50,6 +50,22 @@ void main() {
});
});

group('bimap', () {
test('Right', () {
final value = Either<String, int>.of(10);
final map = value.bimap((l) => "none", (a) => a + 1);
map.matchTestRight((r) {
expect(r, 11);
});
});

test('Left', () {
final value = Either<String, int>.left('abc');
final map = value.bimap((l) => "none", (a) => a + 1);
map.matchTestLeft((l) => expect(l, 'none'));
});
});

group('map2', () {
test('Right', () {
final value = Either<String, int>.of(10);
Expand Down Expand Up @@ -1007,4 +1023,41 @@ void main() {
},
);
});

test('rights', () {
final list = [
right<String, int>(1),
right<String, int>(2),
left<String, int>('a'),
left<String, int>('b'),
right<String, int>(3),
];
final result = Either.rights(list);
expect(result, [1, 2, 3]);
});

test('lefts', () {
final list = [
right<String, int>(1),
right<String, int>(2),
left<String, int>('a'),
left<String, int>('b'),
right<String, int>(3),
];
final result = Either.lefts(list);
expect(result, ['a', 'b']);
});

test('partitionEithers', () {
final list = [
right<String, int>(1),
right<String, int>(2),
left<String, int>('a'),
left<String, int>('b'),
right<String, int>(3),
];
final result = Either.partitionEithers(list);
expect(result.first, ['a', 'b']);
expect(result.second, [1, 2, 3]);
});
}
32 changes: 32 additions & 0 deletions test/src/io_either_test.dart
Expand Up @@ -160,6 +160,38 @@ void main() {
});
});

group('mapLeft', () {
test('Right', () {
final task = IOEither<String, int>(() => Either.of(10));
final ap = task.mapLeft((l) => l.length);
final r = ap.run();
r.matchTestRight((r) => expect(r, 10));
});

test('Left', () {
final task = IOEither<String, int>(() => Either.left('abc'));
final ap = task.mapLeft((l) => l.length);
final r = ap.run();
r.matchTestLeft((l) => expect(l, 3));
});
});

group('bimap', () {
test('Right', () {
final task = IOEither<String, int>(() => Either.of(10));
final ap = task.bimap((l) => l.length, (r) => r / 2);
final r = ap.run();
r.matchTestRight((r) => expect(r, 5.0));
});

test('Left', () {
final task = IOEither<String, int>(() => Either.left('abc'));
final ap = task.bimap((l) => l.length, (r) => r / 2);
final r = ap.run();
r.matchTestLeft((l) => expect(l, 3));
});
});

group('map2', () {
test('Right', () {
final task = IOEither<String, int>(() => Either.of(10));
Expand Down
37 changes: 37 additions & 0 deletions test/src/list_extension_test.dart
Expand Up @@ -1236,6 +1236,43 @@ void main() {
});
});
});

test('rightsEither', () {
final list = [
right<String, int>(1),
right<String, int>(2),
left<String, int>('a'),
left<String, int>('b'),
right<String, int>(3),
];
final result = list.rightsEither();
expect(result, [1, 2, 3]);
});

test('leftsEither', () {
final list = [
right<String, int>(1),
right<String, int>(2),
left<String, int>('a'),
left<String, int>('b'),
right<String, int>(3),
];
final result = list.leftsEither();
expect(result, ['a', 'b']);
});

test('partitionEithersEither', () {
final list = [
right<String, int>(1),
right<String, int>(2),
left<String, int>('a'),
left<String, int>('b'),
right<String, int>(3),
];
final result = list.partitionEithersEither();
expect(result.first, ['a', 'b']);
expect(result.second, [1, 2, 3]);
});
});

group('FpdartSequenceIterableTaskOption', () {
Expand Down
7 changes: 7 additions & 0 deletions test/src/tuple_test.dart
Expand Up @@ -39,6 +39,13 @@ void main() {
expect(map.second, '10');
});

test('bimap', () {
const tuple = Tuple2('abc', 10);
final map = tuple.bimap((v1) => v1.length, (v2) => '$v2');
expect(map.first, 3);
expect(map.second, '10');
});

test('apply', () {
const tuple = Tuple2('abc', 10);
final ap = tuple.apply((v1, v2) => v1.length + v2);
Expand Down