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

bindEither in TaskEither #58

Merged
merged 3 commits into from Oct 9, 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
21 changes: 20 additions & 1 deletion example/src/task_either/overview.dart
@@ -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
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
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
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