Skip to content

Commit

Permalink
bindEither in TaskEither (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
SandroMaglione authored Oct 9, 2022
2 parents 059e69b + 122fee5 commit 4d37130
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 23 deletions.
21 changes: 20 additions & 1 deletion example/src/task_either/overview.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import 'package:fpdart/fpdart.dart';

/// From [Future] to [TaskEither]
Future<int> imperative(String str) async {
try {
return int.parse(str);
} catch (e) {
return -1; // What does -1 means? 🤨
}
}

TaskEither<String, int> functional(String str) {
return TaskEither.tryCatch(
() async => int.parse(str),
// Clear error 🪄
(error, stackTrace) => "Parsing error: $error",
);
}

/// What error is that? What is [dynamic]?
Future<int> asyncI() {
return Future<int>.error('Some error!')
Expand Down Expand Up @@ -34,4 +51,6 @@ TaskEither<int, double> bimapExample(TaskEither<String, int> taskEither) =>
TaskEither<String, int> toTaskEitherExample(Either<String, int> taskEither) =>
taskEither.toTaskEither();

void main() {}
/// Chain [Either] to [TaskEither]
TaskEither<String, int> binding =
TaskEither<String, String>.of("String").bindEither(Either.of(20));
28 changes: 13 additions & 15 deletions lib/src/either.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,6 @@ abstract class Either<L, R> extends HKT2<_EitherHKT, L, R>
Either<L, C> ap<C>(covariant Either<L, C Function(R r)> a) =>
a.flatMap((f) => map(f));

/// Used to chain multiple functions that return a [Either].
///
/// You can extract the value of every [Right] in the chain without
/// handling all possible missing cases.
/// If any of the functions in the chain returns [Left], the result is [Left].
///
/// Same as `bind`.
@override
Either<L, C> flatMap<C>(covariant Either<L, C> Function(R a) f);

/// If this [Either] is a [Right], then return the result of calling `then`.
/// Otherwise return [Left].
@override
Expand Down Expand Up @@ -159,20 +149,28 @@ abstract class Either<L, R> extends HKT2<_EitherHKT, L, R>
@override
Either<L, B> call<B>(covariant Either<L, B> chain) => flatMap((_) => chain);

/// If `f` applied on this [Either] as [Right] returns `true`, then return this [Either].
/// If it returns `false`, return the result of `onFalse` in a [Left].
Either<L, R> filterOrElse(bool Function(R r) f, L Function(R r) onFalse) =>
flatMap((r) => f(r) ? Either.of(r) : Either.left(onFalse(r)));

/// {@template fpdart_flat_map_either}
/// Used to chain multiple functions that return a [Either].
///
/// You can extract the value of every [Right] in the chain without
/// handling all possible missing cases.
/// If any of the functions in the chain returns [Left], the result is [Left].
/// {@endtemplate}
///
/// Same as `bind`.
@override
Either<L, C> flatMap<C>(covariant Either<L, C> Function(R a) f);

/// {@macro fpdart_flat_map_either}
///
/// Same as `flatMap`.
Either<L, R2> bind<R2>(Either<L, R2> Function(R r) f) => flatMap(f);

/// If `f` applied on this [Either] as [Right] returns `true`, then return this [Either].
/// If it returns `false`, return the result of `onFalse` in a [Left].
Either<L, R> filterOrElse(bool Function(R r) f, L Function(R r) onFalse) =>
flatMap((r) => f(r) ? Either.of(r) : Either.left(onFalse(r)));

/// Chain a request that returns another [Either], execute it, ignore
/// the result, and return the same value as the current [Either].
@override
Expand Down
36 changes: 29 additions & 7 deletions lib/src/task_either.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class TaskEither<L, R> extends HKT2<_TaskEitherHKT, L, R>
),
));

/// Chain an [Either] to [TaskEither] by converting it from sync to async.
TaskEither<L, C> bindEither<C>(Either<L, C> either) =>
flatMap((_) => either.toTaskEither());

/// Returns a [TaskEither] that returns a `Right(a)`.
@override
TaskEither<L, C> pure<C>(C a) => TaskEither(() async => Right(a));
Expand Down Expand Up @@ -200,10 +204,31 @@ class TaskEither<L, R> extends HKT2<_TaskEitherHKT, L, R>
factory TaskEither.fromEither(Either<L, R> either) =>
TaskEither(() async => either);

/// Converts a [Future] that may throw to a [Future] that never throws
/// but returns a [Either] instead.
/// {@template fpdart_try_catch_task_either}
/// Execute an async function ([Future]) and convert the result to [Either]:
/// - If the execution is successful, returns a [Right]
/// - If the execution fails (`throw`), then return a [Left] based on `onError`
///
/// Used to work with [Future] and exceptions using [Either] instead of `try`/`catch`.
/// {@endtemplate}
/// ```dart
/// /// From [Future] to [TaskEither]
/// Future<int> imperative(String str) async {
/// try {
/// return int.parse(str);
/// } catch (e) {
/// return -1; /// What does -1 means? 🤨
/// }
/// }
///
/// Used to handle asynchronous computations that may throw using [Either].
/// TaskEither<String, int> functional(String str) {
/// return TaskEither.tryCatch(
/// () async => int.parse(str),
/// /// Clear error 🪄
/// (error, stackTrace) => "Parsing error: $error",
/// );
/// }
/// ```
factory TaskEither.tryCatch(Future<R> Function() run,
L Function(Object error, StackTrace stackTrace) onError) =>
TaskEither<L, R>(() async {
Expand Down Expand Up @@ -304,10 +329,7 @@ class TaskEither<L, R> extends HKT2<_TaskEitherHKT, L, R>
) =>
traverseListSeq(list, identity);

/// Converts a [Future] that may throw to a [Future] that never throws
/// but returns a [Either] instead.
///
/// Used to handle asynchronous computations that may throw using [Either].
/// {@macro fpdart_try_catch_task_either}
///
/// It wraps the `TaskEither.tryCatch` factory to make chaining with `flatMap`
/// easier.
Expand Down
16 changes: 16 additions & 0 deletions test/src/task_either_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,22 @@ void main() {
});
});

group('bindEither', () {
test('Right', () async {
final task = TaskEither<String, int>(() async => Either.of(10));
final ap = task.bindEither(Either.of(2.5));
final r = await ap.run();
r.matchTestRight((r) => expect(r, 2.5));
});

test('Left', () async {
final task = TaskEither<String, int>(() async => Either.left('abc'));
final ap = task.bindEither(Either.of(2.5));
final r = await ap.run();
r.matchTestLeft((l) => expect(l, 'abc'));
});
});

group('ap', () {
test('Right', () async {
final task = TaskEither<String, int>(() async => Either.of(10));
Expand Down

0 comments on commit 4d37130

Please sign in to comment.