Skip to content

Commit

Permalink
TaskOption
Browse files Browse the repository at this point in the history
  • Loading branch information
SandroMaglione committed Jul 5, 2021
1 parent e0f5961 commit e70f577
Show file tree
Hide file tree
Showing 5 changed files with 486 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Added `Compose` and `Compose2`, used to easily compose functions in a chain.
- Added `curry` and `uncurry` extensions on functions up to 5 parameters.
- Completed `TaskOption` type implementation, documentation, and testing

# v0.0.6 - 29 June 2021

Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Would you like to know more about functional programming, fpdart, and how to use
- [x] `IO`
- [x] `Iterable` (`List`) `extension`
- [x] `IOEither`
- [ ] `TaskOption`
- [x] `TaskOption`
- [ ] `ReaderEither`
- [ ] `ReaderTask`
- [ ] `ReaderTaskEither`
Expand All @@ -61,7 +61,7 @@ Would you like to know more about functional programming, fpdart, and how to use
```yaml
# pubspec.yaml
dependencies:
fpdart: ^0.0.6 # Check out the latest version
fpdart: ^0.0.7 # Check out the latest version
```
## ✨ Examples
Expand Down Expand Up @@ -231,10 +231,10 @@ The roadmap for types development is highlighted below (breaking changes to _'st
- ~~Implementation~~
- ~~Documentation~~
- ~~Testing~~
11. `TaskOption`
- Implementation
- Documentation
- Testing
11. ~~`TaskOption`~~
- ~~Implementation~~
- ~~Documentation~~
- ~~Testing~~
12. `ReaderEither`
- Implementation
- Documentation
Expand Down
1 change: 1 addition & 0 deletions lib/fpdart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export 'src/reader.dart';
export 'src/state.dart';
export 'src/task.dart';
export 'src/task_either.dart';
export 'src/task_option.dart';
export 'src/tuple.dart';
export 'src/typeclass/typeclass.export.dart';
export 'src/typedef.dart';
Expand Down
161 changes: 161 additions & 0 deletions lib/src/task_option.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import 'package:fpdart/fpdart.dart';
import 'package:fpdart/src/task.dart';

/// Tag the [HKT] interface for the actual [TaskOption].
abstract class _TaskOptionHKT {}

/// `TaskOption<R>` represents an asynchronous computation that
/// may fails yielding a [None] or returns a `Some(R)` when successful.
///
/// If you want to represent an asynchronous computation that never fails, see [Task].
///
/// If you want to represent an asynchronous computation that returns an object when it fails,
/// see [TaskEither].
class TaskOption<R> extends HKT<_TaskOptionHKT, R>
with Monad<_TaskOptionHKT, R>, Alt<_TaskOptionHKT, R> {
final Future<Option<R>> Function() _run;

/// Build a [TaskOption] from a function returning a `Future<Option<R>>`.
const TaskOption(this._run);

/// Used to chain multiple functions that return a [TaskOption].
///
/// You can extract the value of every [Some] in the chain without
/// handling all possible missing cases.
/// If running any of the tasks in the chain returns [None], the result is [None].
@override
TaskOption<C> flatMap<C>(covariant TaskOption<C> Function(R r) f) =>
TaskOption(() => run().then(
(option) async => option.match(
(r) => f(r).run(),
() => Option.none(),
),
));

/// Returns a [TaskOption] that returns `Some(c)`.
@override
TaskOption<C> pure<C>(C c) => TaskOption(() async => Option.of(c));

/// Change the return type of this [TaskOption] based on its value of type `R` and the
/// value of type `C` of another [TaskOption].
@override
TaskOption<D> map2<C, D>(
covariant TaskOption<C> m1, D Function(R b, C c) f) =>
flatMap((b) => m1.map((c) => f(b, c)));

/// Change the return type of this [TaskOption] based on its value of type `R`, the
/// value of type `C` of a second [TaskOption], and the value of type `D`
/// of a third [TaskOption].
@override
TaskOption<E> map3<C, D, E>(covariant TaskOption<C> m1,
covariant TaskOption<D> m2, E Function(R b, C c, D d) f) =>
flatMap((b) => m1.flatMap((c) => m2.map((d) => f(b, c, d))));

/// If running this [TaskOption] returns [Some], then return the result of calling `then`.
/// Otherwise return [None].
@override
TaskOption<C> andThen<C>(covariant TaskOption<C> Function() then) =>
flatMap((_) => then());

/// If running this [TaskOption] returns [Some], then change its value from type `R` to
/// type `C` using function `f`.
@override
TaskOption<C> map<C>(C Function(R r) f) => ap(pure(f));

/// Apply the function contained inside `a` to change the value on the [Some] from
/// type `R` to a value of type `C`.
@override
TaskOption<C> ap<C>(covariant TaskOption<C Function(R r)> a) =>
a.flatMap((f) => flatMap((v) => pure(f(v))));

/// When this [TaskOption] returns [Some], then return the current [TaskOption].
/// Otherwise return the result of `orElse`.
///
/// Used to provide an **alt**ernative [TaskOption] in case the current one returns [None].
@override
TaskOption<R> alt(covariant TaskOption<R> Function() orElse) => TaskOption(
() async => (await run()).match((_) => run(), () => orElse().run()));

/// When this [TaskOption] returns a [None] then return the result of `orElse`.
/// Otherwise return this [TaskOption].
TaskOption<R> orElse<TL>(TaskOption<R> Function() orElse) =>
TaskOption(() async => (await run())
.match((r) => TaskOption<R>.some(r).run(), () => orElse().run()));

/// Convert this [TaskOption] to a [Task].
///
/// The task returns a [Some] when [TaskOption] returns [Some].
/// Otherwise map the type `L` of [TaskOption] to type `R` by calling `orElse`.
Task<R> getOrElse(R Function() orElse) =>
Task(() async => (await run()).match(identity, orElse));

/// Pattern matching to convert a [TaskOption] to a [Task].
///
/// Execute `onNone` when running this [TaskOption] returns a [None].
/// Otherwise execute `onSome`.
Task<A> match<A>(A Function() onNone, A Function(R r) onSome) =>
Task(() async => (await run()).match(onSome, onNone));

/// Creates a [TaskOption] that will complete after a time delay specified by a [Duration].
TaskOption<R> delay(Duration duration) =>
TaskOption(() => Future.delayed(duration, run));

/// Run the task and return a `Future<Option<R>>`.
Future<Option<R>> run() => _run();

/// Build a [TaskOption] that returns a `Some(r)`.
///
/// Same of `TaskOption.some`.
factory TaskOption.of(R r) => TaskOption(() async => Option.of(r));

/// Flat a [TaskOption] contained inside another [TaskOption] to be a single [TaskOption].
factory TaskOption.flatten(TaskOption<TaskOption<R>> taskOption) =>
taskOption.flatMap(identity);

/// Build a [TaskOption] that returns a `Some(r)`.
///
/// Same of `TaskOption.of`.
factory TaskOption.some(R r) => TaskOption(() async => Option.of(r));

/// Build a [TaskOption] that returns a [None].
factory TaskOption.none() => TaskOption(() async => Option.none());

/// Build a [TaskOption] from the result of running `task`.
factory TaskOption.fromTask(Task<R> task) =>
TaskOption(() async => Option.of(await task.run()));

/// When calling `predicate` with `value` returns `true`, then running [TaskOption] returns `Some(value)`.
/// Otherwise return [None].
factory TaskOption.fromPredicate(R value, bool Function(R a) predicate) =>
TaskOption(
() async => predicate(value) ? Option.of(value) : Option.none());

/// Converts a [Future] that may throw to a [Future] that never throws
/// but returns a [Option] instead.
///
/// Used to handle asynchronous computations that may throw using [Option].
factory TaskOption.tryCatch(Future<R> Function() run) =>
TaskOption<R>(() async {
try {
return Option.of(await run());
} catch (_) {
return Option.none();
}
});

/// Build a [TaskOption] from `either` that returns [None] when
/// `either` is [Left], otherwise it returns [Some].
static TaskOption<R> fromEither<L, R>(Either<L, R> either) =>
TaskOption(() async => either.match((_) => Option.none(), some));

/// Converts a [Future] that may throw to a [Future] that never throws
/// but returns a [Option] instead.
///
/// Used to handle asynchronous computations that may throw using [Option].
///
/// It wraps the `TaskOption.tryCatch` factory to make chaining with `flatMap`
/// easier.
static TaskOption<R> Function(A a) tryCatchK<R, A>(
Future<R> Function(A a) run) =>
(a) => TaskOption.tryCatch(() => run(a));
}
Loading

0 comments on commit e70f577

Please sign in to comment.