From f98455f32b64b291eed731f553efc030b1a61b01 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 17:30:55 +0200 Subject: [PATCH 01/71] v1.0.0 min sdk v3.0.0 --- packages/fpdart/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fpdart/pubspec.yaml b/packages/fpdart/pubspec.yaml index 031d1cb4..464669a8 100644 --- a/packages/fpdart/pubspec.yaml +++ b/packages/fpdart/pubspec.yaml @@ -2,7 +2,7 @@ name: fpdart description: > Functional programming in Dart and Flutter. All the main functional programming types and patterns fully documented, tested, and with examples. -version: 0.6.0 +version: 1.0.0 homepage: https://www.sandromaglione.com/ repository: https://github.com/SandroMaglione/fpdart author: Maglione Sandro @@ -10,7 +10,7 @@ documentation: https://www.sandromaglione.com/course/fpdart-functional-programmi issue_tracker: https://github.com/SandroMaglione/fpdart/issues environment: - sdk: ">=2.17.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dev_dependencies: lints: ^2.0.1 From 67ab709b069086e834e522b6a26d39b025dfadc7 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 17:04:47 +0200 Subject: [PATCH 02/71] final and sealed Option, Either, Unit --- packages/fpdart/lib/src/either.dart | 6 +++--- packages/fpdart/lib/src/option.dart | 6 +++--- packages/fpdart/lib/src/unit.dart | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/fpdart/lib/src/either.dart b/packages/fpdart/lib/src/either.dart index f8165f7d..ccfa399e 100644 --- a/packages/fpdart/lib/src/either.dart +++ b/packages/fpdart/lib/src/either.dart @@ -15,7 +15,7 @@ Either right(R r) => Right(r); /// Shortcut for `Either.left(l)`. Either left(L l) => Left(l); -class _EitherThrow { +final class _EitherThrow { final L value; const _EitherThrow(this.value); } @@ -29,7 +29,7 @@ DoAdapterEither _doAdapter() => typedef DoFunctionEither = R Function(DoAdapterEither $); /// Tag the [HKT2] interface for the actual [Either]. -abstract class _EitherHKT {} +abstract final class _EitherHKT {} /// Represents a value of one of two possible types, [Left] or [Right]. /// @@ -37,7 +37,7 @@ abstract class _EitherHKT {} /// values when a computation may fail (such as `-1`, `null`, etc.), we return an instance /// of [Right] containing the correct result when a computation is successful, otherwise we return /// an instance of [Left] containing information about the kind of error that occurred. -abstract class Either extends HKT2<_EitherHKT, L, R> +sealed class Either extends HKT2<_EitherHKT, L, R> with Functor2<_EitherHKT, L, R>, Applicative2<_EitherHKT, L, R>, diff --git a/packages/fpdart/lib/src/option.dart b/packages/fpdart/lib/src/option.dart index bc807c46..a152588d 100644 --- a/packages/fpdart/lib/src/option.dart +++ b/packages/fpdart/lib/src/option.dart @@ -36,7 +36,7 @@ Option optionOf(T? t) => Option.fromNullable(t); Option option(T value, bool Function(T) predicate) => Option.fromPredicate(value, predicate); -class _OptionThrow { +final class _OptionThrow { const _OptionThrow(); } @@ -47,7 +47,7 @@ A _doAdapter(Option option) => typedef DoFunctionOption = A Function(DoAdapterOption $); /// Tag the [HKT] interface for the actual [Option]. -abstract class _OptionHKT {} +abstract final class _OptionHKT {} // `Option implements Functor` expresses correctly the // return type of the `map` function as `HKT`. @@ -72,7 +72,7 @@ abstract class _OptionHKT {} /// printString, /// ); /// ``` -abstract class Option extends HKT<_OptionHKT, T> +sealed class Option extends HKT<_OptionHKT, T> with Functor<_OptionHKT, T>, Applicative<_OptionHKT, T>, diff --git a/packages/fpdart/lib/src/unit.dart b/packages/fpdart/lib/src/unit.dart index ba3943bb..ad72ba7d 100644 --- a/packages/fpdart/lib/src/unit.dart +++ b/packages/fpdart/lib/src/unit.dart @@ -4,7 +4,7 @@ /// to understand why it is better to not use `void` and use [Unit] instead. /// /// There is only one value of type [Unit]. -class Unit { +final class Unit { static const Unit _unit = Unit._instance(); const Unit._instance(); From b5d0f6ec786afee24da69e3d1fa4e17559284ef2 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 17:08:33 +0200 Subject: [PATCH 03/71] updated CHANGELOG for v1.0.0 --- packages/fpdart/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 7d6d4518..deb6deb8 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -1,3 +1,10 @@ +## v1.0.0 - Soon +- `Either` as `sealed` class + - You can now use exhaustive pattern matching (`Left` or `Right`) +- `Option` as `sealed` class + - You can now use exhaustive pattern matching (`None` or `Some`) +- `Unit` type as `final` class (no `extends` nor `implements`) + ## v0.6.0 - 6 May 2023 - Do notation [#97](https://github.com/SandroMaglione/fpdart/pull/97) (Special thanks to [@tim-smart](https://github.com/tim-smart) 🎉) - All the main types now have a **`Do()` constructor** used to initialize a Do notation chain From ddffd51dd9dc8518213aedb775c03417aa5d888f Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 17:13:59 +0200 Subject: [PATCH 04/71] final classes --- packages/fpdart/CHANGELOG.md | 14 +++++++++++++- packages/fpdart/lib/src/io.dart | 4 ++-- packages/fpdart/lib/src/io_either.dart | 6 +++--- packages/fpdart/lib/src/io_option.dart | 6 +++--- packages/fpdart/lib/src/io_ref.dart | 2 +- packages/fpdart/lib/src/predicate.dart | 2 +- packages/fpdart/lib/src/reader.dart | 4 ++-- packages/fpdart/lib/src/state.dart | 4 ++-- packages/fpdart/lib/src/state_async.dart | 4 ++-- packages/fpdart/lib/src/task.dart | 4 ++-- packages/fpdart/lib/src/task_either.dart | 6 +++--- packages/fpdart/lib/src/task_option.dart | 6 +++--- 12 files changed, 37 insertions(+), 25 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index deb6deb8..781e94cd 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -3,7 +3,19 @@ - You can now use exhaustive pattern matching (`Left` or `Right`) - `Option` as `sealed` class - You can now use exhaustive pattern matching (`None` or `Some`) -- `Unit` type as `final` class (no `extends` nor `implements`) +- Types marked as `final` (no `extends` nor `implements`) + - `Unit` + - `Predicate` + - `Reader` + - `State` + - `StateAsync` + - `IO` + - `IORef` + - `IOOption` + - `IOEither` + - `Task` + - `TaskOption` + - `TaskEither` ## v0.6.0 - 6 May 2023 - Do notation [#97](https://github.com/SandroMaglione/fpdart/pull/97) (Special thanks to [@tim-smart](https://github.com/tim-smart) 🎉) diff --git a/packages/fpdart/lib/src/io.dart b/packages/fpdart/lib/src/io.dart index aea696e0..91c7bd0d 100644 --- a/packages/fpdart/lib/src/io.dart +++ b/packages/fpdart/lib/src/io.dart @@ -17,13 +17,13 @@ A _doAdapter(IO io) => io.run(); typedef DoFunctionIO = A Function(DoAdapterIO $); /// Tag the [HKT] interface for the actual [Option]. -abstract class _IOHKT {} +abstract final class _IOHKT {} /// `IO` represents a **non-deterministic synchronous** computation that /// can **cause side effects**, yields a value of type `A` and **never fails**. /// /// If you want to represent a synchronous computation that may fail, see [IOEither]. -class IO extends HKT<_IOHKT, A> +final class IO extends HKT<_IOHKT, A> with Functor<_IOHKT, A>, Applicative<_IOHKT, A>, Monad<_IOHKT, A> { final A Function() _run; diff --git a/packages/fpdart/lib/src/io_either.dart b/packages/fpdart/lib/src/io_either.dart index 511e2e9e..06bcba14 100644 --- a/packages/fpdart/lib/src/io_either.dart +++ b/packages/fpdart/lib/src/io_either.dart @@ -9,7 +9,7 @@ import 'typeclass/functor.dart'; import 'typeclass/hkt.dart'; import 'typeclass/monad.dart'; -class _IOEitherThrow { +final class _IOEitherThrow { final L value; const _IOEitherThrow(this.value); } @@ -21,14 +21,14 @@ DoAdapterIOEither _doAdapter() => typedef DoFunctionIOEither = A Function(DoAdapterIOEither $); /// Tag the [HKT2] interface for the actual [IOEither]. -abstract class _IOEitherHKT {} +abstract final class _IOEitherHKT {} /// `IOEither` represents a **non-deterministic synchronous** computation that /// can **cause side effects**, yields a value of type `R` or **can fail** by returning /// a value of type `L`. /// /// If you want to represent a synchronous computation that may never fail, see [IO]. -class IOEither extends HKT2<_IOEitherHKT, L, R> +final class IOEither extends HKT2<_IOEitherHKT, L, R> with Functor2<_IOEitherHKT, L, R>, Applicative2<_IOEitherHKT, L, R>, diff --git a/packages/fpdart/lib/src/io_option.dart b/packages/fpdart/lib/src/io_option.dart index 8a361148..07621268 100644 --- a/packages/fpdart/lib/src/io_option.dart +++ b/packages/fpdart/lib/src/io_option.dart @@ -11,7 +11,7 @@ import 'typeclass/functor.dart'; import 'typeclass/hkt.dart'; import 'typeclass/monad.dart'; -class _IOOptionThrow { +final class _IOOptionThrow { const _IOOptionThrow(); } @@ -23,7 +23,7 @@ A _doAdapter(IOOption iOOption) => iOOption.run().getOrElse( typedef DoFunctionIOOption = A Function(DoAdapterIOOption $); /// Tag the [HKT] interface for the actual [IOOption]. -abstract class _IOOptionHKT {} +abstract final class _IOOptionHKT {} /// `IOOption` represents an **synchronous** computation that /// may fails yielding a [None] or returns a `Some(R)` when successful. @@ -32,7 +32,7 @@ abstract class _IOOptionHKT {} /// /// If you want to represent an synchronous computation that returns an object when it fails, /// see [IOEither]. -class IOOption extends HKT<_IOOptionHKT, R> +final class IOOption extends HKT<_IOOptionHKT, R> with Functor<_IOOptionHKT, R>, Applicative<_IOOptionHKT, R>, diff --git a/packages/fpdart/lib/src/io_ref.dart b/packages/fpdart/lib/src/io_ref.dart index 5b6f91f4..258e94b2 100644 --- a/packages/fpdart/lib/src/io_ref.dart +++ b/packages/fpdart/lib/src/io_ref.dart @@ -13,7 +13,7 @@ import 'unit.dart'; /// In most cases, the [State] monad should be used, and [IORef] must be /// viewed as a last resort, as it holds a mutable field inside itself that can /// be modified inside of the [IO] monad. -class IORef { +final class IORef { T _value; IORef._(this._value); diff --git a/packages/fpdart/lib/src/predicate.dart b/packages/fpdart/lib/src/predicate.dart index 31da6f66..8022b2ff 100644 --- a/packages/fpdart/lib/src/predicate.dart +++ b/packages/fpdart/lib/src/predicate.dart @@ -1,5 +1,5 @@ /// Compose functions that given a generic value [T] return a `bool`. -class Predicate { +final class Predicate { final bool Function(T t) _predicate; /// Build a [Predicate] given a function returning a `bool`. diff --git a/packages/fpdart/lib/src/reader.dart b/packages/fpdart/lib/src/reader.dart index 19342c6a..23da4db0 100644 --- a/packages/fpdart/lib/src/reader.dart +++ b/packages/fpdart/lib/src/reader.dart @@ -5,12 +5,12 @@ import 'typeclass/hkt.dart'; import 'typeclass/monad.dart'; /// Tag the [HKT2] interface for the actual [Reader]. -abstract class ReaderHKT {} +abstract final class ReaderHKT {} /// `Reader` allows to read values `A` from a dependency/context `R` /// without explicitly passing the dependency between multiple nested /// function calls. -class Reader extends HKT2 +final class Reader extends HKT2 with Functor2, Applicative2, diff --git a/packages/fpdart/lib/src/state.dart b/packages/fpdart/lib/src/state.dart index af4f984d..8e15cb71 100644 --- a/packages/fpdart/lib/src/state.dart +++ b/packages/fpdart/lib/src/state.dart @@ -5,14 +5,14 @@ import 'typeclass/typeclass.export.dart'; import 'unit.dart'; /// Tag the [HKT2] interface for the actual [State]. -abstract class _StateHKT {} +abstract final class _StateHKT {} /// `State` is used to store, update, and extract state in a functional way. /// /// `S` is a State (e.g. the current _State_ of your Bank Account). /// `A` is value that you _extract out of the [State]_ /// (Account Balance fetched from the current state of your Bank Account `S`). -class State extends HKT2<_StateHKT, S, A> +final class State extends HKT2<_StateHKT, S, A> with Functor2<_StateHKT, S, A>, Applicative2<_StateHKT, S, A>, diff --git a/packages/fpdart/lib/src/state_async.dart b/packages/fpdart/lib/src/state_async.dart index 38213572..92cf3f7d 100644 --- a/packages/fpdart/lib/src/state_async.dart +++ b/packages/fpdart/lib/src/state_async.dart @@ -5,7 +5,7 @@ import 'typeclass/typeclass.export.dart'; import 'unit.dart'; /// Tag the [HKT2] interface for the actual [StateAsync]. -abstract class _StateAsyncHKT {} +abstract final class _StateAsyncHKT {} /// `StateAsync` is used to store, update, and extract async state in a functional way. /// @@ -14,7 +14,7 @@ abstract class _StateAsyncHKT {} /// (Account Balance fetched from the current state of your Bank Account `S`). /// /// Used when fetching and updating the state is **asynchronous**. Use [State] otherwise. -class StateAsync extends HKT2<_StateAsyncHKT, S, A> +final class StateAsync extends HKT2<_StateAsyncHKT, S, A> with Functor2<_StateAsyncHKT, S, A>, Applicative2<_StateAsyncHKT, S, A>, diff --git a/packages/fpdart/lib/src/task.dart b/packages/fpdart/lib/src/task.dart index ab000778..e13b6a62 100644 --- a/packages/fpdart/lib/src/task.dart +++ b/packages/fpdart/lib/src/task.dart @@ -15,12 +15,12 @@ Future _doAdapter(Task task) => task.run(); typedef DoFunctionTask = Future Function(DoAdapterTask $); /// Tag the [HKT] interface for the actual [Task]. -abstract class _TaskHKT {} +abstract final class _TaskHKT {} /// [Task] represents an asynchronous computation that yields a value of type `A` and **never fails**. /// /// If you want to represent an asynchronous computation that may fail, see [TaskEither]. -class Task extends HKT<_TaskHKT, A> +final class Task extends HKT<_TaskHKT, A> with Functor<_TaskHKT, A>, Applicative<_TaskHKT, A>, Monad<_TaskHKT, A> { final Future Function() _run; diff --git a/packages/fpdart/lib/src/task_either.dart b/packages/fpdart/lib/src/task_either.dart index 1c7d3ce3..5e4e2433 100644 --- a/packages/fpdart/lib/src/task_either.dart +++ b/packages/fpdart/lib/src/task_either.dart @@ -8,7 +8,7 @@ import 'typeclass/functor.dart'; import 'typeclass/hkt.dart'; import 'typeclass/monad.dart'; -class _TaskEitherThrow { +final class _TaskEitherThrow { final L value; const _TaskEitherThrow(this.value); } @@ -23,13 +23,13 @@ typedef DoFunctionTaskEither = Future Function( DoAdapterTaskEither $); /// Tag the [HKT2] interface for the actual [TaskEither]. -abstract class _TaskEitherHKT {} +abstract final class _TaskEitherHKT {} /// `TaskEither` represents an asynchronous computation that /// either yields a value of type `R` or fails yielding an error of type `L`. /// /// If you want to represent an asynchronous computation that never fails, see [Task]. -class TaskEither extends HKT2<_TaskEitherHKT, L, R> +final class TaskEither extends HKT2<_TaskEitherHKT, L, R> with Functor2<_TaskEitherHKT, L, R>, Applicative2<_TaskEitherHKT, L, R>, diff --git a/packages/fpdart/lib/src/task_option.dart b/packages/fpdart/lib/src/task_option.dart index c391235e..4f15fd79 100644 --- a/packages/fpdart/lib/src/task_option.dart +++ b/packages/fpdart/lib/src/task_option.dart @@ -9,7 +9,7 @@ import 'typeclass/functor.dart'; import 'typeclass/hkt.dart'; import 'typeclass/monad.dart'; -class _TaskOptionThrow { +final class _TaskOptionThrow { const _TaskOptionThrow(); } @@ -21,7 +21,7 @@ Future _doAdapter(TaskOption taskOption) => taskOption.run().then( typedef DoFunctionTaskOption = Future Function(DoAdapterTaskOption $); /// Tag the [HKT] interface for the actual [TaskOption]. -abstract class _TaskOptionHKT {} +abstract final class _TaskOptionHKT {} /// `TaskOption` represents an **asynchronous** computation that /// may fails yielding a [None] or returns a `Some(R)` when successful. @@ -30,7 +30,7 @@ abstract class _TaskOptionHKT {} /// /// If you want to represent an asynchronous computation that returns an object when it fails, /// see [TaskEither]. -class TaskOption extends HKT<_TaskOptionHKT, R> +final class TaskOption extends HKT<_TaskOptionHKT, R> with Functor<_TaskOptionHKT, R>, Applicative<_TaskOptionHKT, R>, From de8e9dc93eb43af5bd9e4d53745f28501c9108ca Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 17:37:42 +0200 Subject: [PATCH 05/71] removed boolean extension --- packages/fpdart/lib/fpdart.dart | 1 - packages/fpdart/lib/src/boolean.dart | 16 ---------------- 2 files changed, 17 deletions(-) delete mode 100644 packages/fpdart/lib/src/boolean.dart diff --git a/packages/fpdart/lib/fpdart.dart b/packages/fpdart/lib/fpdart.dart index 528ed1ff..b1bce336 100644 --- a/packages/fpdart/lib/fpdart.dart +++ b/packages/fpdart/lib/fpdart.dart @@ -1,4 +1,3 @@ -export 'src/boolean.dart'; export 'src/compose.dart'; export 'src/date.dart'; export 'src/either.dart'; diff --git a/packages/fpdart/lib/src/boolean.dart b/packages/fpdart/lib/src/boolean.dart deleted file mode 100644 index 526a6039..00000000 --- a/packages/fpdart/lib/src/boolean.dart +++ /dev/null @@ -1,16 +0,0 @@ -/// `fpdart` extension methods on [bool] -extension FpdartBoolean on bool { - /// Pattern matching on [bool]. Execute `onFalse` when the value is `false`, - /// otherwise execute `onTrue`. - /// - /// Same as `fold`. - T match(T Function() onFalse, T Function() onTrue) => - this ? onTrue() : onFalse(); - - /// Pattern matching on [bool]. Execute `onFalse` when the value is `false`, - /// otherwise execute `onTrue`. - /// - /// Same as `match`. - T fold(T Function() onFalse, T Function() onTrue) => - match(onFalse, onTrue); -} From 00b6987406ab380bdfa880c660243f5bce495352 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 17:43:39 +0200 Subject: [PATCH 06/71] bool extension removed docs --- packages/fpdart/CHANGELOG.md | 12 +++++++++ packages/fpdart/test/src/boolean_test.dart | 30 ---------------------- 2 files changed, 12 insertions(+), 30 deletions(-) delete mode 100644 packages/fpdart/test/src/boolean_test.dart diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 781e94cd..1e48aee8 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -16,6 +16,18 @@ - `Task` - `TaskOption` - `TaskEither` +- Removed `bool` extension (`match` and `fold`), use the ternary operator or pattern matching instead ⚠️ +```dart +final boolValue = Random().nextBool(); + +/// Before +final result = boolValue.match(() => -1, () => 1); +final result = boolValue.fold(() => -1, () => 1); + +/// New +final result = boolValue ? 1 : -1; +final result = switch (boolValue) { true => 1, false => -1 }; +``` ## v0.6.0 - 6 May 2023 - Do notation [#97](https://github.com/SandroMaglione/fpdart/pull/97) (Special thanks to [@tim-smart](https://github.com/tim-smart) 🎉) diff --git a/packages/fpdart/test/src/boolean_test.dart b/packages/fpdart/test/src/boolean_test.dart deleted file mode 100644 index 15ee67d2..00000000 --- a/packages/fpdart/test/src/boolean_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:test/test.dart'; -import 'package:fpdart/fpdart.dart'; - -void main() { - group('boolean', () { - group('fold', () { - test('true', () { - final ap = true.fold(() => -1, () => 1); - expect(ap, 1); - }); - - test('false', () { - final ap = false.fold(() => -1, () => 1); - expect(ap, -1); - }); - }); - - group('match', () { - test('true', () { - final ap = true.match(() => -1, () => 1); - expect(ap, 1); - }); - - test('false', () { - final ap = false.match(() => -1, () => 1); - expect(ap, -1); - }); - }); - }); -} From 38772b3c0fbfc02a4ed1be2d24ec130cc3732889 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 17:54:30 +0200 Subject: [PATCH 07/71] organized extensions in new internal folder --- packages/fpdart/CHANGELOG.md | 1 + packages/fpdart/lib/fpdart.dart | 4 +- packages/fpdart/lib/src/compose.dart | 16 ---- packages/fpdart/lib/src/date.dart | 15 --- .../lib/src/extension/compose_extension.dart | 15 +++ .../lib/src/extension/curry_extension.dart | 88 ++++++++++++++++++ .../src/extension/date_time_extension.dart | 16 ++++ .../lib/src/extension/extension.export.dart | 7 ++ .../src/{ => extension}/list_extension.dart | 22 ++--- .../src/{ => extension}/map_extension.dart | 11 ++- .../{ => extension}/nullable_extension.dart | 14 +-- .../lib/src/extension/option_extension.dart | 30 ++++++ packages/fpdart/lib/src/function.dart | 91 ------------------- packages/fpdart/lib/src/io_option.dart | 1 + packages/fpdart/lib/src/option.dart | 28 +----- packages/fpdart/lib/src/task.dart | 2 +- packages/fpdart/lib/src/task_option.dart | 1 + 17 files changed, 186 insertions(+), 176 deletions(-) create mode 100644 packages/fpdart/lib/src/extension/compose_extension.dart create mode 100644 packages/fpdart/lib/src/extension/curry_extension.dart create mode 100644 packages/fpdart/lib/src/extension/date_time_extension.dart create mode 100644 packages/fpdart/lib/src/extension/extension.export.dart rename packages/fpdart/lib/src/{ => extension}/list_extension.dart (98%) rename packages/fpdart/lib/src/{ => extension}/map_extension.dart (98%) rename packages/fpdart/lib/src/{ => extension}/nullable_extension.dart (90%) create mode 100644 packages/fpdart/lib/src/extension/option_extension.dart diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 1e48aee8..ad7dbc6b 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -28,6 +28,7 @@ final result = boolValue.fold(() => -1, () => 1); final result = boolValue ? 1 : -1; final result = switch (boolValue) { true => 1, false => -1 }; ``` +- Organized all extensions inside internal `extension` folder ## v0.6.0 - 6 May 2023 - Do notation [#97](https://github.com/SandroMaglione/fpdart/pull/97) (Special thanks to [@tim-smart](https://github.com/tim-smart) 🎉) diff --git a/packages/fpdart/lib/fpdart.dart b/packages/fpdart/lib/fpdart.dart index b1bce336..f18f83ec 100644 --- a/packages/fpdart/lib/fpdart.dart +++ b/packages/fpdart/lib/fpdart.dart @@ -1,14 +1,12 @@ export 'src/compose.dart'; export 'src/date.dart'; export 'src/either.dart'; +export 'src/extension/extension.export.dart'; export 'src/function.dart'; export 'src/io.dart'; export 'src/io_either.dart'; export 'src/io_option.dart'; export 'src/io_ref.dart'; -export 'src/list_extension.dart'; -export 'src/map_extension.dart'; -export 'src/nullable_extension.dart'; export 'src/option.dart'; export 'src/predicate.dart'; export 'src/random.dart'; diff --git a/packages/fpdart/lib/src/compose.dart b/packages/fpdart/lib/src/compose.dart index d9e64288..b707678b 100644 --- a/packages/fpdart/lib/src/compose.dart +++ b/packages/fpdart/lib/src/compose.dart @@ -263,19 +263,3 @@ class _ChainCompose22 _composeC2_2(_composeC2_1(_input1c2_1, _input2c2_1), _inputC2_2); } - -// --- Extensions below 👇 --- // - -extension ComposeOnFunction1 on Output Function(Input) { - /// Build a [Compose] from the function that can be used to - /// easily compose functions in a chain. - Compose c(Input input) => Compose(this, input); -} - -extension ComposeOnFunction2 on Output Function( - Input1, Input2) { - /// Build a [Compose2] from the function that can be used to - /// easily compose functions in a chain. - Compose2 c(Input1 input1, Input2 input2) => - Compose2(this, input1, input2); -} diff --git a/packages/fpdart/lib/src/date.dart b/packages/fpdart/lib/src/date.dart index d8e0410f..335a5f9c 100644 --- a/packages/fpdart/lib/src/date.dart +++ b/packages/fpdart/lib/src/date.dart @@ -2,21 +2,6 @@ import 'io.dart'; import 'typeclass/eq.dart'; import 'typeclass/order.dart'; -/// `fpdart` extension methods on [DateTime] -extension FpdartDateTime on DateTime { - /// Return `true` when this [DateTime] and `other` have the same **year**. - bool eqvYear(DateTime other) => dateEqYear.eqv(this, other); - - /// Return `true` when this [DateTime] and `other` have the same **month**. - bool eqvMonth(DateTime other) => dateEqMonth.eqv(this, other); - - /// Return `true` when this [DateTime] and `other` have the same **day**. - bool eqvDay(DateTime other) => dateEqDay.eqv(this, other); - - /// Return `true` when this [DateTime] and `other` have the same **year, month, and day**. - bool eqvYearMonthDay(DateTime other) => dateEqYearMonthDay.eqv(this, other); -} - /// Constructs a [DateTime] instance with current date and time in the local time zone. /// /// [IO] wrapper around dart `DateTime.now()`. diff --git a/packages/fpdart/lib/src/extension/compose_extension.dart b/packages/fpdart/lib/src/extension/compose_extension.dart new file mode 100644 index 00000000..34c74df7 --- /dev/null +++ b/packages/fpdart/lib/src/extension/compose_extension.dart @@ -0,0 +1,15 @@ +import '../compose.dart'; + +extension ComposeOnFunction1 on Output Function(Input) { + /// Build a [Compose] from the function that can be used to + /// easily compose functions in a chain. + Compose c(Input input) => Compose(this, input); +} + +extension ComposeOnFunction2 on Output Function( + Input1, Input2) { + /// Build a [Compose2] from the function that can be used to + /// easily compose functions in a chain. + Compose2 c(Input1 input1, Input2 input2) => + Compose2(this, input1, input2); +} diff --git a/packages/fpdart/lib/src/extension/curry_extension.dart b/packages/fpdart/lib/src/extension/curry_extension.dart new file mode 100644 index 00000000..88d97d94 --- /dev/null +++ b/packages/fpdart/lib/src/extension/curry_extension.dart @@ -0,0 +1,88 @@ +extension CurryExtension2 on Output Function( + Input1, Input2) { + /// Convert this function from accepting two parameters to a function + /// that returns another function both accepting one parameter. + /// + /// Inverse of `uncurry`. + Output Function(Input2) curry(Input1 input1) => + (input2) => this(input1, input2); +} + +extension UncurryExtension2 on Output Function(Input2) + Function(Input1) { + /// Convert a function that returns another function to a single function + /// accepting two parameters. + /// + /// Inverse of `curry`. + Output Function(Input1, Input2) uncurry() => + (Input1 input1, Input2 input2) => this(input1)(input2); +} + +extension CurryExtension3 on Output Function( + Input1, Input2, Input3) { + /// Convert this function from accepting three parameters to a series + /// of functions that all accept one parameter. + /// + /// Inverse of `uncurry`. + Output Function(Input3) Function(Input2) curry(Input1 input1) => + (input2) => (input3) => this(input1, input2, input3); +} + +extension UncurryExtension3 + on Output Function(Input3) Function(Input2) Function(Input1) { + /// Convert a function that returns a series of functions to a single function + /// accepting three parameters. + /// + /// Inverse of `curry`. + Output Function(Input1, Input2, Input3) uncurry() => + (Input1 input1, Input2 input2, Input3 input3) => + this(input1)(input2)(input3); +} + +extension CurryExtension4 on Output + Function(Input1, Input2, Input3, Input4) { + /// Convert this function from accepting four parameters to a series + /// of functions that all accept one parameter. + /// + /// Inverse of `uncurry`. + Output Function(Input4) Function(Input3) Function(Input2) curry( + Input1 input1) => + (input2) => (input3) => (input4) => this(input1, input2, input3, input4); +} + +extension UncurryExtension4 + on Output Function(Input4) Function(Input3) Function(Input2) Function( + Input1) { + /// Convert a function that returns a series of functions to a single function + /// accepting four parameters. + /// + /// Inverse of `curry`. + Output Function(Input1, Input2, Input3, Input4) uncurry() => + (Input1 input1, Input2 input2, Input3 input3, Input4 input4) => + this(input1)(input2)(input3)(input4); +} + +extension CurryExtension5 + on Output Function(Input1, Input2, Input3, Input4, Input5) { + /// Convert this function from accepting five parameters to a series + /// of functions that all accept one parameter. + /// + /// Inverse of `uncurry`. + Output Function(Input5) Function(Input4) Function(Input3) Function(Input2) + curry(Input1 input1) => (input2) => (input3) => + (input4) => (input5) => this(input1, input2, input3, input4, input5); +} + +extension UncurryExtension5 + on Output Function(Input5) Function(Input4) Function(Input3) Function( + Input2) + Function(Input1) { + /// Convert a function that returns a series of functions to a single function + /// accepting five parameters. + /// + /// Inverse of `curry`. + Output Function(Input1, Input2, Input3, Input4, Input5) uncurry() => + (Input1 input1, Input2 input2, Input3 input3, Input4 input4, + Input5 input5) => + this(input1)(input2)(input3)(input4)(input5); +} diff --git a/packages/fpdart/lib/src/extension/date_time_extension.dart b/packages/fpdart/lib/src/extension/date_time_extension.dart new file mode 100644 index 00000000..e3fddb44 --- /dev/null +++ b/packages/fpdart/lib/src/extension/date_time_extension.dart @@ -0,0 +1,16 @@ +import '../date.dart'; + +/// `fpdart` extension methods on [DateTime] +extension FpdartDateTime on DateTime { + /// Return `true` when this [DateTime] and `other` have the same **year**. + bool eqvYear(DateTime other) => dateEqYear.eqv(this, other); + + /// Return `true` when this [DateTime] and `other` have the same **month**. + bool eqvMonth(DateTime other) => dateEqMonth.eqv(this, other); + + /// Return `true` when this [DateTime] and `other` have the same **day**. + bool eqvDay(DateTime other) => dateEqDay.eqv(this, other); + + /// Return `true` when this [DateTime] and `other` have the same **year, month, and day**. + bool eqvYearMonthDay(DateTime other) => dateEqYearMonthDay.eqv(this, other); +} diff --git a/packages/fpdart/lib/src/extension/extension.export.dart b/packages/fpdart/lib/src/extension/extension.export.dart new file mode 100644 index 00000000..f4129ecc --- /dev/null +++ b/packages/fpdart/lib/src/extension/extension.export.dart @@ -0,0 +1,7 @@ +export 'compose_extension.dart'; +export 'curry_extension.dart'; +export 'date_time_extension.dart'; +export 'list_extension.dart'; +export 'map_extension.dart'; +export 'nullable_extension.dart'; +export 'option_extension.dart'; diff --git a/packages/fpdart/lib/src/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart similarity index 98% rename from packages/fpdart/lib/src/list_extension.dart rename to packages/fpdart/lib/src/extension/list_extension.dart index 58df2087..265c0af9 100644 --- a/packages/fpdart/lib/src/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -1,14 +1,14 @@ -import 'date.dart'; -import 'either.dart'; -import 'io.dart'; -import 'io_either.dart'; -import 'io_option.dart'; -import 'option.dart'; -import 'task.dart'; -import 'task_either.dart'; -import 'task_option.dart'; -import 'tuple.dart'; -import 'typeclass/order.dart'; +import '../date.dart'; +import '../either.dart'; +import '../io.dart'; +import '../io_either.dart'; +import '../io_option.dart'; +import '../option.dart'; +import '../task.dart'; +import '../task_either.dart'; +import '../task_option.dart'; +import '../tuple.dart'; +import '../typeclass/order.dart'; /// Functional programming functions on a mutable dart [Iterable] using `fpdart`. extension FpdartOnMutableIterable on Iterable { diff --git a/packages/fpdart/lib/src/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart similarity index 98% rename from packages/fpdart/lib/src/map_extension.dart rename to packages/fpdart/lib/src/extension/map_extension.dart index 672b3199..f4b3274f 100644 --- a/packages/fpdart/lib/src/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -1,9 +1,10 @@ +import '../option.dart'; +import '../tuple.dart'; +import '../typeclass/eq.dart'; +import '../typeclass/order.dart'; +import '../typedef.dart'; import 'list_extension.dart'; -import 'option.dart'; -import 'tuple.dart'; -import 'typeclass/eq.dart'; -import 'typeclass/order.dart'; -import 'typedef.dart'; +import 'option_extension.dart'; /// Functional programming functions on a mutable dart [Map] using `fpdart`. extension FpdartOnMutableMap on Map { diff --git a/packages/fpdart/lib/src/nullable_extension.dart b/packages/fpdart/lib/src/extension/nullable_extension.dart similarity index 90% rename from packages/fpdart/lib/src/nullable_extension.dart rename to packages/fpdart/lib/src/extension/nullable_extension.dart index ae7ca932..8e3d80a9 100644 --- a/packages/fpdart/lib/src/nullable_extension.dart +++ b/packages/fpdart/lib/src/extension/nullable_extension.dart @@ -1,10 +1,10 @@ -import 'either.dart'; -import 'io_either.dart'; -import 'io_option.dart'; -import 'option.dart'; -import 'task.dart'; -import 'task_either.dart'; -import 'task_option.dart'; +import '../either.dart'; +import '../io_either.dart'; +import '../io_option.dart'; +import '../option.dart'; +import '../task.dart'; +import '../task_either.dart'; +import '../task_option.dart'; /// `fpdart` extension to work on nullable values `T?` extension FpdartOnNullable on T? { diff --git a/packages/fpdart/lib/src/extension/option_extension.dart b/packages/fpdart/lib/src/extension/option_extension.dart new file mode 100644 index 00000000..6e63d0fd --- /dev/null +++ b/packages/fpdart/lib/src/extension/option_extension.dart @@ -0,0 +1,30 @@ +import '../function.dart'; +import '../option.dart'; +import '../typeclass/eq.dart'; + +extension OptionExtension on Option { + /// Return the current [Option] if it is a [Some], otherwise return the result of `orElse`. + /// + /// Used to provide an **alt**ernative [Option] in case the current one is [None]. + /// ```dart + /// [🍌].alt(() => [🍎]) -> [🍌] + /// [_].alt(() => [🍎]) -> [🍎] + /// ``` + Option alt(Option Function() orElse) => + this is Some ? this : orElse(); + + /// Return `true` when value of `a` is equal to the value inside the [Option]. + bool elem(T t, Eq eq) => match(() => false, (value) => eq.eqv(value, t)); + + /// If this [Option] is a [Some] then return the value inside the [Option]. + /// Otherwise return the result of `orElse`. + /// ```dart + /// [🍌].getOrElse(() => 🍎) -> 🍌 + /// [_].getOrElse(() => 🍎) -> 🍎 + /// + /// 👆 same as 👇 + /// + /// [🍌].match(() => 🍎, (🍌) => 🍌) + /// ``` + T getOrElse(T Function() orElse) => match(orElse, identity); +} diff --git a/packages/fpdart/lib/src/function.dart b/packages/fpdart/lib/src/function.dart index 4f63ee69..d1a18ecb 100644 --- a/packages/fpdart/lib/src/function.dart +++ b/packages/fpdart/lib/src/function.dart @@ -155,94 +155,3 @@ F Function(A a, B b, C c, D d, E e) uncurry5( function) { return (A a, B b, C c, D d, E e) => function(a)(b)(c)(d)(e); } - -// --- Extensions below 👇 --- // - -extension CurryExtension2 on Output Function( - Input1, Input2) { - /// Convert this function from accepting two parameters to a function - /// that returns another function both accepting one parameter. - /// - /// Inverse of `uncurry`. - Output Function(Input2) curry(Input1 input1) => - (input2) => this(input1, input2); -} - -extension UncurryExtension2 on Output Function(Input2) - Function(Input1) { - /// Convert a function that returns another function to a single function - /// accepting two parameters. - /// - /// Inverse of `curry`. - Output Function(Input1, Input2) uncurry() => - (Input1 input1, Input2 input2) => this(input1)(input2); -} - -extension CurryExtension3 on Output Function( - Input1, Input2, Input3) { - /// Convert this function from accepting three parameters to a series - /// of functions that all accept one parameter. - /// - /// Inverse of `uncurry`. - Output Function(Input3) Function(Input2) curry(Input1 input1) => - (input2) => (input3) => this(input1, input2, input3); -} - -extension UncurryExtension3 - on Output Function(Input3) Function(Input2) Function(Input1) { - /// Convert a function that returns a series of functions to a single function - /// accepting three parameters. - /// - /// Inverse of `curry`. - Output Function(Input1, Input2, Input3) uncurry() => - (Input1 input1, Input2 input2, Input3 input3) => - this(input1)(input2)(input3); -} - -extension CurryExtension4 on Output - Function(Input1, Input2, Input3, Input4) { - /// Convert this function from accepting four parameters to a series - /// of functions that all accept one parameter. - /// - /// Inverse of `uncurry`. - Output Function(Input4) Function(Input3) Function(Input2) curry( - Input1 input1) => - (input2) => (input3) => (input4) => this(input1, input2, input3, input4); -} - -extension UncurryExtension4 - on Output Function(Input4) Function(Input3) Function(Input2) Function( - Input1) { - /// Convert a function that returns a series of functions to a single function - /// accepting four parameters. - /// - /// Inverse of `curry`. - Output Function(Input1, Input2, Input3, Input4) uncurry() => - (Input1 input1, Input2 input2, Input3 input3, Input4 input4) => - this(input1)(input2)(input3)(input4); -} - -extension CurryExtension5 - on Output Function(Input1, Input2, Input3, Input4, Input5) { - /// Convert this function from accepting five parameters to a series - /// of functions that all accept one parameter. - /// - /// Inverse of `uncurry`. - Output Function(Input5) Function(Input4) Function(Input3) Function(Input2) - curry(Input1 input1) => (input2) => (input3) => - (input4) => (input5) => this(input1, input2, input3, input4, input5); -} - -extension UncurryExtension5 - on Output Function(Input5) Function(Input4) Function(Input3) Function( - Input2) - Function(Input1) { - /// Convert a function that returns a series of functions to a single function - /// accepting five parameters. - /// - /// Inverse of `curry`. - Output Function(Input1, Input2, Input3, Input4, Input5) uncurry() => - (Input1 input1, Input2 input2, Input3 input3, Input4 input4, - Input5 input5) => - this(input1)(input2)(input3)(input4)(input5); -} diff --git a/packages/fpdart/lib/src/io_option.dart b/packages/fpdart/lib/src/io_option.dart index 07621268..385a2141 100644 --- a/packages/fpdart/lib/src/io_option.dart +++ b/packages/fpdart/lib/src/io_option.dart @@ -1,4 +1,5 @@ import 'either.dart'; +import 'extension/option_extension.dart'; import 'function.dart'; import 'io.dart'; import 'io_either.dart'; diff --git a/packages/fpdart/lib/src/option.dart b/packages/fpdart/lib/src/option.dart index a152588d..1798a529 100644 --- a/packages/fpdart/lib/src/option.dart +++ b/packages/fpdart/lib/src/option.dart @@ -1,4 +1,5 @@ import 'either.dart'; +import 'extension/option_extension.dart'; import 'function.dart'; import 'io_option.dart'; import 'task_option.dart'; @@ -616,30 +617,3 @@ class None extends Option { @override Object? toJson(Object? Function(Never p1) toJsonT) => null; } - -extension OptionExtension on Option { - /// Return the current [Option] if it is a [Some], otherwise return the result of `orElse`. - /// - /// Used to provide an **alt**ernative [Option] in case the current one is [None]. - /// ```dart - /// [🍌].alt(() => [🍎]) -> [🍌] - /// [_].alt(() => [🍎]) -> [🍎] - /// ``` - Option alt(Option Function() orElse) => - this is Some ? this : orElse(); - - /// Return `true` when value of `a` is equal to the value inside the [Option]. - bool elem(T t, Eq eq) => match(() => false, (value) => eq.eqv(value, t)); - - /// If this [Option] is a [Some] then return the value inside the [Option]. - /// Otherwise return the result of `orElse`. - /// ```dart - /// [🍌].getOrElse(() => 🍎) -> 🍌 - /// [_].getOrElse(() => 🍎) -> 🍎 - /// - /// 👆 same as 👇 - /// - /// [🍌].match(() => 🍎, (🍌) => 🍌) - /// ``` - T getOrElse(T Function() orElse) => match(orElse, identity); -} diff --git a/packages/fpdart/lib/src/task.dart b/packages/fpdart/lib/src/task.dart index e13b6a62..d29d9171 100644 --- a/packages/fpdart/lib/src/task.dart +++ b/packages/fpdart/lib/src/task.dart @@ -1,6 +1,6 @@ import 'either.dart'; +import 'extension/list_extension.dart'; import 'function.dart'; -import 'list_extension.dart'; import 'option.dart'; import 'task_either.dart'; import 'task_option.dart'; diff --git a/packages/fpdart/lib/src/task_option.dart b/packages/fpdart/lib/src/task_option.dart index 4f15fd79..b5b31fbe 100644 --- a/packages/fpdart/lib/src/task_option.dart +++ b/packages/fpdart/lib/src/task_option.dart @@ -1,4 +1,5 @@ import 'either.dart'; +import 'extension/option_extension.dart'; import 'function.dart'; import 'option.dart'; import 'task.dart'; From 40a6419428de4cbf8234470d0d3c7aeb24f1589a Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 18:07:11 +0200 Subject: [PATCH 08/71] string conversions extension --- .../lib/src/extension/extension.export.dart | 1 + .../lib/src/extension/string_extension.dart | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/fpdart/lib/src/extension/string_extension.dart diff --git a/packages/fpdart/lib/src/extension/extension.export.dart b/packages/fpdart/lib/src/extension/extension.export.dart index f4129ecc..10e87089 100644 --- a/packages/fpdart/lib/src/extension/extension.export.dart +++ b/packages/fpdart/lib/src/extension/extension.export.dart @@ -5,3 +5,4 @@ export 'list_extension.dart'; export 'map_extension.dart'; export 'nullable_extension.dart'; export 'option_extension.dart'; +export 'string_extension.dart'; diff --git a/packages/fpdart/lib/src/extension/string_extension.dart b/packages/fpdart/lib/src/extension/string_extension.dart new file mode 100644 index 00000000..e56a5a2f --- /dev/null +++ b/packages/fpdart/lib/src/extension/string_extension.dart @@ -0,0 +1,34 @@ +import '../either.dart'; +import '../option.dart'; +import 'nullable_extension.dart'; + +/// Functional programming functions on dart [String] using `fpdart`. +extension FpdartOnString on String { + /// Convert this [String] to [num], returns [None] for invalid inputs. + Option get toNumOption => num.tryParse(this).toOption(); + + /// Convert this [String] to [int], returns [None] for invalid inputs. + Option get toIntOption => int.tryParse(this).toOption(); + + /// Convert this [String] to [double], returns [None] for invalid inputs. + Option get toDoubleOption => double.tryParse(this).toOption(); + + /// Convert this [String] to [bool], returns [None] for invalid inputs. + Option get toBooleanOption => bool.tryParse(this).toOption(); + + /// Convert this [String] to [num], returns the result of `onLeft` for invalid inputs. + Either toNumEither(L Function() onLeft) => + num.tryParse(this).toEither(onLeft); + + /// Convert this [String] to [int], returns the result of `onLeft` for invalid inputs. + Either toIntEither(L Function() onLeft) => + int.tryParse(this).toEither(onLeft); + + /// Convert this [String] to [double], returns the result of `onLeft` for invalid inputs. + Either toDoubleEither(L Function() onLeft) => + double.tryParse(this).toEither(onLeft); + + /// Convert this [String] to [bool], returns the result of `onLeft` for invalid inputs. + Either toBooleanEither(L Function() onLeft) => + bool.tryParse(this).toEither(onLeft); +} From f2aae3cc33a75bd286280702303fd53c458e3740 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 18:39:03 +0200 Subject: [PATCH 09/71] testing conversions to option --- packages/fpdart/example/src/map/overview.dart | 2 +- .../lib/src/extension/string_extension.dart | 4 +- .../src/extension/string_extension_test.dart | 400 ++++++++++++++++++ 3 files changed, 403 insertions(+), 3 deletions(-) create mode 100644 packages/fpdart/test/src/extension/string_extension_test.dart diff --git a/packages/fpdart/example/src/map/overview.dart b/packages/fpdart/example/src/map/overview.dart index a73595a8..88b7a10d 100644 --- a/packages/fpdart/example/src/map/overview.dart +++ b/packages/fpdart/example/src/map/overview.dart @@ -1,5 +1,5 @@ import 'package:fpdart/src/date.dart'; -import 'package:fpdart/src/map_extension.dart'; +import 'package:fpdart/src/extension/map_extension.dart'; void main() { final d1 = DateTime(2001, 1, 1); diff --git a/packages/fpdart/lib/src/extension/string_extension.dart b/packages/fpdart/lib/src/extension/string_extension.dart index e56a5a2f..5d4b8ea8 100644 --- a/packages/fpdart/lib/src/extension/string_extension.dart +++ b/packages/fpdart/lib/src/extension/string_extension.dart @@ -14,7 +14,7 @@ extension FpdartOnString on String { Option get toDoubleOption => double.tryParse(this).toOption(); /// Convert this [String] to [bool], returns [None] for invalid inputs. - Option get toBooleanOption => bool.tryParse(this).toOption(); + Option get toBoolOption => bool.tryParse(this).toOption(); /// Convert this [String] to [num], returns the result of `onLeft` for invalid inputs. Either toNumEither(L Function() onLeft) => @@ -29,6 +29,6 @@ extension FpdartOnString on String { double.tryParse(this).toEither(onLeft); /// Convert this [String] to [bool], returns the result of `onLeft` for invalid inputs. - Either toBooleanEither(L Function() onLeft) => + Either toBoolEither(L Function() onLeft) => bool.tryParse(this).toEither(onLeft); } diff --git a/packages/fpdart/test/src/extension/string_extension_test.dart b/packages/fpdart/test/src/extension/string_extension_test.dart new file mode 100644 index 00000000..5ebfe791 --- /dev/null +++ b/packages/fpdart/test/src/extension/string_extension_test.dart @@ -0,0 +1,400 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:test/test.dart'; + +import '../utils/match_utils.dart'; + +void main() { + group('FpdartOnString', () { + group('toNumOption', () { + group('Some', () { + test('"10"', () { + final result = "10".toNumOption; + result.matchTestSome((t) { + expect(t, 10); + }); + }); + + test('"10.0"', () { + final result = "10.0".toNumOption; + result.matchTestSome((t) { + expect(t, 10.0); + }); + }); + + test('"10.5"', () { + final result = "10.5".toNumOption; + result.matchTestSome((t) { + expect(t, 10.5); + }); + }); + + test('"10.512"', () { + final result = "10.512".toNumOption; + result.matchTestSome((t) { + expect(t, 10.512); + }); + }); + + test('" 10 "', () { + final result = " 10 ".toNumOption; + result.matchTestSome((t) { + expect(t, 10); + }); + }); + + test('"-10"', () { + final result = "-10".toNumOption; + result.matchTestSome((t) { + expect(t, -10); + }); + }); + + test('" 3.14 \xA0"', () { + final result = " 3.14 \xA0".toNumOption; + result.matchTestSome((t) { + expect(t, 3.14); + }); + }); + + test('"0."', () { + final result = "0.".toNumOption; + result.matchTestSome((t) { + expect(t, 0); + }); + }); + + test('".0"', () { + final result = ".0".toNumOption; + result.matchTestSome((t) { + expect(t, 0); + }); + }); + + test('"-1.e3"', () { + final result = "-1.e3".toNumOption; + result.matchTestSome((t) { + expect(t, -1000); + }); + }); + + test('"1234E+7"', () { + final result = "1234E+7".toNumOption; + result.matchTestSome((t) { + expect(t, 12340000000); + }); + }); + + test('"0xFF"', () { + final result = "0xFF".toNumOption; + result.matchTestSome((t) { + expect(t, 255); + }); + }); + }); + + group('None', () { + test('"- 10"', () { + final result = "- 10".toNumOption; + expect(result, isA()); + }); + + test('"10,0"', () { + final result = "10,0".toNumOption; + expect(result, isA()); + }); + + test('"10a"', () { + final result = "10a".toNumOption; + expect(result, isA()); + }); + + test('"none"', () { + final result = "none".toNumOption; + expect(result, isA()); + }); + + test('"10.512a"', () { + final result = "10.512a".toNumOption; + expect(result, isA()); + }); + + test('"1f"', () { + final result = "1f".toNumOption; + expect(result, isA()); + }); + }); + }); + + group('toIntOption', () { + group('Some', () { + test('"10"', () { + final result = "10".toIntOption; + result.matchTestSome((t) { + expect(t, 10); + }); + }); + + test('"-10"', () { + final result = "-10".toIntOption; + result.matchTestSome((t) { + expect(t, -10); + }); + }); + + test('" 10 "', () { + final result = " 10 ".toIntOption; + result.matchTestSome((t) { + expect(t, 10); + }); + }); + + test('"0xFF"', () { + final result = "0xFF".toIntOption; + result.matchTestSome((t) { + expect(t, 255); + }); + }); + }); + + group('None', () { + test('"- 10"', () { + final result = "- 10".toIntOption; + expect(result, isA()); + }); + + test('"-1.e3"', () { + final result = "-1.e3".toIntOption; + expect(result, isA()); + }); + + test('"1234E+7"', () { + final result = "1234E+7".toIntOption; + expect(result, isA()); + }); + + test('"0."', () { + final result = "0.".toIntOption; + expect(result, isA()); + }); + + test('"10.5"', () { + final result = "10.5".toIntOption; + expect(result, isA()); + }); + + test('"10.0"', () { + final result = "10.0".toIntOption; + expect(result, isA()); + }); + + test('"10.512"', () { + final result = "10.512".toIntOption; + expect(result, isA()); + }); + + test('" 3.14 \xA0"', () { + final result = " 3.14 \xA0".toIntOption; + expect(result, isA()); + }); + + test('".0"', () { + final result = ".0".toIntOption; + expect(result, isA()); + }); + + test('"10,0"', () { + final result = "10,0".toIntOption; + expect(result, isA()); + }); + + test('"10a"', () { + final result = "10a".toIntOption; + expect(result, isA()); + }); + + test('"none"', () { + final result = "none".toIntOption; + expect(result, isA()); + }); + + test('"10.512a"', () { + final result = "10.512a".toIntOption; + expect(result, isA()); + }); + + test('"1f"', () { + final result = "1f".toIntOption; + expect(result, isA()); + }); + }); + }); + + group('toDoubleOption', () { + group('Some', () { + test('"10"', () { + final result = "10".toDoubleOption; + result.matchTestSome((t) { + expect(t, 10.0); + }); + }); + + test('"10.0"', () { + final result = "10.0".toDoubleOption; + result.matchTestSome((t) { + expect(t, 10.0); + }); + }); + + test('"10.5"', () { + final result = "10.5".toDoubleOption; + result.matchTestSome((t) { + expect(t, 10.5); + }); + }); + + test('"10.512"', () { + final result = "10.512".toDoubleOption; + result.matchTestSome((t) { + expect(t, 10.512); + }); + }); + + test('" 10 "', () { + final result = " 10 ".toDoubleOption; + result.matchTestSome((t) { + expect(t, 10.0); + }); + }); + + test('"-10"', () { + final result = "-10".toDoubleOption; + result.matchTestSome((t) { + expect(t, -10.0); + }); + }); + + test('" 3.14 \xA0"', () { + final result = " 3.14 \xA0".toDoubleOption; + result.matchTestSome((t) { + expect(t, 3.14); + }); + }); + + test('"0."', () { + final result = "0.".toDoubleOption; + result.matchTestSome((t) { + expect(t, 0.0); + }); + }); + + test('".0"', () { + final result = ".0".toDoubleOption; + result.matchTestSome((t) { + expect(t, 0.0); + }); + }); + + test('"-1.e3"', () { + final result = "-1.e3".toDoubleOption; + result.matchTestSome((t) { + expect(t, -1000.0); + }); + }); + + test('"1234E+7"', () { + final result = "1234E+7".toDoubleOption; + result.matchTestSome((t) { + expect(t, 12340000000.0); + }); + }); + }); + + group('None', () { + test('"0xFF"', () { + final result = "0xFF".toDoubleOption; + expect(result, isA()); + }); + + test('"- 10"', () { + final result = "- 10".toDoubleOption; + expect(result, isA()); + }); + + test('"10,0"', () { + final result = "10,0".toDoubleOption; + expect(result, isA()); + }); + + test('"10a"', () { + final result = "10a".toDoubleOption; + expect(result, isA()); + }); + + test('"none"', () { + final result = "none".toDoubleOption; + expect(result, isA()); + }); + + test('"10.512a"', () { + final result = "10.512a".toDoubleOption; + expect(result, isA()); + }); + + test('"1f"', () { + final result = "1f".toDoubleOption; + expect(result, isA()); + }); + }); + }); + + group('toBoolOption', () { + group('Some', () { + test('"true"', () { + final result = "true".toBoolOption; + result.matchTestSome((t) { + expect(t, true); + }); + }); + + test('"false"', () { + final result = "false".toBoolOption; + result.matchTestSome((t) { + expect(t, false); + }); + }); + }); + + group('None', () { + test('"TRUE"', () { + final result = "TRUE".toBoolOption; + expect(result, isA()); + }); + + test('"FALSE"', () { + final result = "FALSE".toBoolOption; + expect(result, isA()); + }); + + test('"NO"', () { + final result = "NO".toBoolOption; + expect(result, isA()); + }); + + test('"YES"', () { + final result = "YES".toBoolOption; + expect(result, isA()); + }); + + test('"0"', () { + final result = "0".toBoolOption; + expect(result, isA()); + }); + + test('"1"', () { + final result = "1".toBoolOption; + expect(result, isA()); + }); + }); + }); + }); +} From 748880d4ff2a0c1a5054b92c49a343ecd6c8f197 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 18:55:23 +0200 Subject: [PATCH 10/71] conversions as functions --- .../lib/src/extension/string_extension.dart | 8 +++--- packages/fpdart/lib/src/function.dart | 23 +++++++++++++++ packages/fpdart/test/src/function_test.dart | 28 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 packages/fpdart/test/src/function_test.dart diff --git a/packages/fpdart/lib/src/extension/string_extension.dart b/packages/fpdart/lib/src/extension/string_extension.dart index 5d4b8ea8..c96c586a 100644 --- a/packages/fpdart/lib/src/extension/string_extension.dart +++ b/packages/fpdart/lib/src/extension/string_extension.dart @@ -4,16 +4,16 @@ import 'nullable_extension.dart'; /// Functional programming functions on dart [String] using `fpdart`. extension FpdartOnString on String { - /// Convert this [String] to [num], returns [None] for invalid inputs. + /// {@macro fpdart_string_extension_to_num_option} Option get toNumOption => num.tryParse(this).toOption(); - /// Convert this [String] to [int], returns [None] for invalid inputs. + /// {@macro fpdart_string_extension_to_int_option} Option get toIntOption => int.tryParse(this).toOption(); - /// Convert this [String] to [double], returns [None] for invalid inputs. + /// {@macro fpdart_string_extension_to_double_option} Option get toDoubleOption => double.tryParse(this).toOption(); - /// Convert this [String] to [bool], returns [None] for invalid inputs. + /// {@macro fpdart_string_extension_to_bool_option} Option get toBoolOption => bool.tryParse(this).toOption(); /// Convert this [String] to [num], returns the result of `onLeft` for invalid inputs. diff --git a/packages/fpdart/lib/src/function.dart b/packages/fpdart/lib/src/function.dart index d1a18ecb..61440b3c 100644 --- a/packages/fpdart/lib/src/function.dart +++ b/packages/fpdart/lib/src/function.dart @@ -1,3 +1,6 @@ +import 'extension/string_extension.dart'; +import 'option.dart'; + typedef Endo = A Function(A a); /// Returns the given `a`. @@ -155,3 +158,23 @@ F Function(A a, B b, C c, D d, E e) uncurry5( function) { return (A a, B b, C c, D d, E e) => function(a)(b)(c)(d)(e); } + +/// {@template fpdart_string_extension_to_num_option} +/// Convert this [String] to [num], returns [None] for invalid inputs. +/// {@endtemplate} +Option toNumOption(String str) => str.toNumOption; + +/// {@template fpdart_string_extension_to_int_option} +/// Convert this [String] to [int], returns [None] for invalid inputs. +/// {@endtemplate} +Option toIntOption(String str) => str.toIntOption; + +/// {@template fpdart_string_extension_to_double_option} +/// Convert this [String] to [double], returns [None] for invalid inputs. +/// {@endtemplate} +Option toDoubleOption(String str) => str.toDoubleOption; + +/// {@template fpdart_string_extension_to_bool_option} +/// Convert this [String] to [bool], returns [None] for invalid inputs. +/// {@endtemplate} +Option toBoolOption(String str) => str.toBoolOption; diff --git a/packages/fpdart/test/src/function_test.dart b/packages/fpdart/test/src/function_test.dart new file mode 100644 index 00000000..fb7b2cb0 --- /dev/null +++ b/packages/fpdart/test/src/function_test.dart @@ -0,0 +1,28 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:glados/glados.dart'; + +void main() { + group('function', () { + group('[Property-based testing]', () { + Glados(any.letterOrDigits).test('toNumOption (same as extension)', + (stringValue) { + expect(stringValue.toNumOption, toNumOption(stringValue)); + }); + + Glados(any.letterOrDigits).test('toIntOption (same as extension)', + (stringValue) { + expect(stringValue.toIntOption, toIntOption(stringValue)); + }); + + Glados(any.letterOrDigits).test('toDoubleOption (same as extension)', + (stringValue) { + expect(stringValue.toDoubleOption, toDoubleOption(stringValue)); + }); + + Glados(any.letterOrDigits).test('toBoolOption (same as extension)', + (stringValue) { + expect(stringValue.toBoolOption, toBoolOption(stringValue)); + }); + }); + }); +} From a74284fcf2889d864c3b98baabac427c1c07eee8 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 19:09:23 +0200 Subject: [PATCH 11/71] testing string extensions either --- .../lib/src/extension/string_extension.dart | 8 +- packages/fpdart/lib/src/function.dart | 25 + .../src/extension/string_extension_test.dart | 458 ++++++++++++++++++ packages/fpdart/test/src/function_test.dart | 24 + 4 files changed, 511 insertions(+), 4 deletions(-) diff --git a/packages/fpdart/lib/src/extension/string_extension.dart b/packages/fpdart/lib/src/extension/string_extension.dart index c96c586a..06ad665e 100644 --- a/packages/fpdart/lib/src/extension/string_extension.dart +++ b/packages/fpdart/lib/src/extension/string_extension.dart @@ -16,19 +16,19 @@ extension FpdartOnString on String { /// {@macro fpdart_string_extension_to_bool_option} Option get toBoolOption => bool.tryParse(this).toOption(); - /// Convert this [String] to [num], returns the result of `onLeft` for invalid inputs. + /// {@macro fpdart_string_extension_to_num_either} Either toNumEither(L Function() onLeft) => num.tryParse(this).toEither(onLeft); - /// Convert this [String] to [int], returns the result of `onLeft` for invalid inputs. + /// {@macro fpdart_string_extension_to_int_either} Either toIntEither(L Function() onLeft) => int.tryParse(this).toEither(onLeft); - /// Convert this [String] to [double], returns the result of `onLeft` for invalid inputs. + /// {@macro fpdart_string_extension_to_double_either} Either toDoubleEither(L Function() onLeft) => double.tryParse(this).toEither(onLeft); - /// Convert this [String] to [bool], returns the result of `onLeft` for invalid inputs. + /// {@macro fpdart_string_extension_to_bool_either} Either toBoolEither(L Function() onLeft) => bool.tryParse(this).toEither(onLeft); } diff --git a/packages/fpdart/lib/src/function.dart b/packages/fpdart/lib/src/function.dart index 61440b3c..c01ab1cf 100644 --- a/packages/fpdart/lib/src/function.dart +++ b/packages/fpdart/lib/src/function.dart @@ -1,3 +1,4 @@ +import 'either.dart'; import 'extension/string_extension.dart'; import 'option.dart'; @@ -178,3 +179,27 @@ Option toDoubleOption(String str) => str.toDoubleOption; /// Convert this [String] to [bool], returns [None] for invalid inputs. /// {@endtemplate} Option toBoolOption(String str) => str.toBoolOption; + +/// {@template fpdart_string_extension_to_num_either} +/// Convert this [String] to [num], returns the result of `onLeft` for invalid inputs. +/// {@endtemplate} +Either Function(String) toNumEither(L Function() onLeft) => + (str) => str.toNumEither(onLeft); + +/// {@template fpdart_string_extension_to_int_either} +/// Convert this [String] to [int], returns the result of `onLeft` for invalid inputs. +/// {@endtemplate} +Either Function(String) toIntEither(L Function() onLeft) => + (str) => str.toIntEither(onLeft); + +/// {@template fpdart_string_extension_to_double_either} +/// Convert this [String] to [double], returns the result of `onLeft` for invalid inputs. +/// {@endtemplate} +Either Function(String) toDoubleEither(L Function() onLeft) => + (str) => str.toDoubleEither(onLeft); + +/// {@template fpdart_string_extension_to_bool_either} +/// Convert this [String] to [bool], returns the result of `onLeft` for invalid inputs. +/// {@endtemplate} +Either Function(String) toBoolEither(L Function() onLeft) => + (str) => str.toBoolEither(onLeft); diff --git a/packages/fpdart/test/src/extension/string_extension_test.dart b/packages/fpdart/test/src/extension/string_extension_test.dart index 5ebfe791..af54be4e 100644 --- a/packages/fpdart/test/src/extension/string_extension_test.dart +++ b/packages/fpdart/test/src/extension/string_extension_test.dart @@ -396,5 +396,463 @@ void main() { }); }); }); + + group('toNumEither', () { + group('Right', () { + test('"10"', () { + final result = "10".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10); + }); + }); + + test('"10.0"', () { + final result = "10.0".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.0); + }); + }); + + test('"10.5"', () { + final result = "10.5".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.5); + }); + }); + + test('"10.512"', () { + final result = "10.512".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.512); + }); + }); + + test('" 10 "', () { + final result = " 10 ".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10); + }); + }); + + test('"-10"', () { + final result = "-10".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, -10); + }); + }); + + test('" 3.14 \xA0"', () { + final result = " 3.14 \xA0".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 3.14); + }); + }); + + test('"0."', () { + final result = "0.".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 0); + }); + }); + + test('".0"', () { + final result = ".0".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 0); + }); + }); + + test('"-1.e3"', () { + final result = "-1.e3".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, -1000); + }); + }); + + test('"1234E+7"', () { + final result = "1234E+7".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 12340000000); + }); + }); + + test('"0xFF"', () { + final result = "0xFF".toNumEither(() => "left"); + result.matchTestRight((t) { + expect(t, 255); + }); + }); + }); + + group('Left', () { + test('"- 10"', () { + final result = "- 10".toNumEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10,0"', () { + final result = "10,0".toNumEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10a"', () { + final result = "10a".toNumEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"none"', () { + final result = "none".toNumEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10.512a"', () { + final result = "10.512a".toNumEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"1f"', () { + final result = "1f".toNumEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + }); + }); + + group('toIntEither', () { + group('Some', () { + test('"10"', () { + final result = "10".toIntEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10); + }); + }); + + test('"-10"', () { + final result = "-10".toIntEither(() => "left"); + result.matchTestRight((t) { + expect(t, -10); + }); + }); + + test('" 10 "', () { + final result = " 10 ".toIntEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10); + }); + }); + + test('"0xFF"', () { + final result = "0xFF".toIntEither(() => "left"); + result.matchTestRight((t) { + expect(t, 255); + }); + }); + }); + + group('None', () { + test('"- 10"', () { + final result = "- 10".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"-1.e3"', () { + final result = "-1.e3".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"1234E+7"', () { + final result = "1234E+7".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"0."', () { + final result = "0.".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10.5"', () { + final result = "10.5".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10.0"', () { + final result = "10.0".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10.512"', () { + final result = "10.512".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('" 3.14 \xA0"', () { + final result = " 3.14 \xA0".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('".0"', () { + final result = ".0".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10,0"', () { + final result = "10,0".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10a"', () { + final result = "10a".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"none"', () { + final result = "none".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10.512a"', () { + final result = "10.512a".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"1f"', () { + final result = "1f".toIntEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + }); + }); + + group('toDoubleEither', () { + group('Some', () { + test('"10"', () { + final result = "10".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.0); + }); + }); + + test('"10.0"', () { + final result = "10.0".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.0); + }); + }); + + test('"10.5"', () { + final result = "10.5".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.5); + }); + }); + + test('"10.512"', () { + final result = "10.512".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.512); + }); + }); + + test('" 10 "', () { + final result = " 10 ".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 10.0); + }); + }); + + test('"-10"', () { + final result = "-10".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, -10.0); + }); + }); + + test('" 3.14 \xA0"', () { + final result = " 3.14 \xA0".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 3.14); + }); + }); + + test('"0."', () { + final result = "0.".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 0.0); + }); + }); + + test('".0"', () { + final result = ".0".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 0.0); + }); + }); + + test('"-1.e3"', () { + final result = "-1.e3".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, -1000.0); + }); + }); + + test('"1234E+7"', () { + final result = "1234E+7".toDoubleEither(() => "left"); + result.matchTestRight((t) { + expect(t, 12340000000.0); + }); + }); + }); + + group('None', () { + test('"0xFF"', () { + final result = "0xFF".toDoubleEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"- 10"', () { + final result = "- 10".toDoubleEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10,0"', () { + final result = "10,0".toDoubleEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10a"', () { + final result = "10a".toDoubleEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"none"', () { + final result = "none".toDoubleEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"10.512a"', () { + final result = "10.512a".toDoubleEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"1f"', () { + final result = "1f".toDoubleEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + }); + }); + + group('toBoolEither', () { + group('Some', () { + test('"true"', () { + final result = "true".toBoolEither(() => "left"); + result.matchTestRight((t) { + expect(t, true); + }); + }); + + test('"false"', () { + final result = "false".toBoolEither(() => "left"); + result.matchTestRight((t) { + expect(t, false); + }); + }); + }); + + group('None', () { + test('"TRUE"', () { + final result = "TRUE".toBoolEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"FALSE"', () { + final result = "FALSE".toBoolEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"NO"', () { + final result = "NO".toBoolEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"YES"', () { + final result = "YES".toBoolEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"0"', () { + final result = "0".toBoolEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + + test('"1"', () { + final result = "1".toBoolEither(() => "left"); + result.matchTestLeft((t) { + expect(t, "left"); + }); + }); + }); + }); }); } diff --git a/packages/fpdart/test/src/function_test.dart b/packages/fpdart/test/src/function_test.dart index fb7b2cb0..d3c55701 100644 --- a/packages/fpdart/test/src/function_test.dart +++ b/packages/fpdart/test/src/function_test.dart @@ -23,6 +23,30 @@ void main() { (stringValue) { expect(stringValue.toBoolOption, toBoolOption(stringValue)); }); + + Glados(any.letterOrDigits).test('toNumEither (same as extension)', + (stringValue) { + expect(stringValue.toNumEither(() => "left"), + toNumEither(() => "left")(stringValue)); + }); + + Glados(any.letterOrDigits).test('toIntEither (same as extension)', + (stringValue) { + expect(stringValue.toIntEither(() => "left"), + toIntEither(() => "left")(stringValue)); + }); + + Glados(any.letterOrDigits).test('toDoubleEither (same as extension)', + (stringValue) { + expect(stringValue.toDoubleEither(() => "left"), + toDoubleEither(() => "left")(stringValue)); + }); + + Glados(any.letterOrDigits).test('toBoolEither (same as extension)', + (stringValue) { + expect(stringValue.toBoolEither(() => "left"), + toBoolEither(() => "left")(stringValue)); + }); }); }); } From 2e340841505a448c029e714890e0cbff5fd461e7 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 18 May 2023 19:15:47 +0200 Subject: [PATCH 12/71] string extensions in CHANGELOG --- packages/fpdart/CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index ad7dbc6b..6adc6b58 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -16,6 +16,30 @@ - `Task` - `TaskOption` - `TaskEither` +- Added conversions helpers from `String` to `num`, `int`, `double`, and `bool` using `Option` and `Either` (both as extension methods on `String` and as functions) + - `toNumOption` + - `toIntOption` + - `toDoubleOption` + - `toBoolOption` + - `toNumEither` + - `toIntEither` + - `toDoubleEither` + - `toBoolEither` +```dart +/// As extension on [String] +final result = "10".toNumOption; /// `Some(10)` +final result = "10.5".toNumOption; /// `Some(10.5)` +final result = "0xFF".toIntOption; /// `Some(255)` +final result = "10.5".toDoubleOption; /// `Some(10.5)` +final result = "NO".toBoolEither(() => "left"); /// `Left("left")` + +/// As functions +final result = toNumOption("10"); /// `Some(10)` +final result = toNumOption("10.5"); /// `Some(10.5)` +final result = toIntOption("0xFF"); /// `Some(255)` +final result = toDoubleOption("10.5"); /// `Some(10.5)` +final result = toBoolEither(() => "left")("NO"); /// `Left("left")` +``` - Removed `bool` extension (`match` and `fold`), use the ternary operator or pattern matching instead ⚠️ ```dart final boolValue = Random().nextBool(); From bc881baa94312d1d49bb341e8f6539f3d98a1458 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 17:13:45 +0200 Subject: [PATCH 13/71] refactor extension tests --- .../src/extension/date_time_extension.dart | 2 +- .../lib/src/extension/option_extension.dart | 2 +- packages/fpdart/test/src/date_test.dart | 57 ---------------- .../extension/date_time_extension_test.dart | 66 +++++++++++++++++++ .../{ => extension}/list_extension_test.dart | 2 +- .../{ => extension}/map_extension_test.dart | 2 +- .../nullable_extension_test.dart | 2 +- .../src/extension/option_extension_test.dart | 44 +++++++++++++ packages/fpdart/test/src/option_test.dart | 37 ----------- 9 files changed, 115 insertions(+), 99 deletions(-) create mode 100644 packages/fpdart/test/src/extension/date_time_extension_test.dart rename packages/fpdart/test/src/{ => extension}/list_extension_test.dart (99%) rename packages/fpdart/test/src/{ => extension}/map_extension_test.dart (99%) rename packages/fpdart/test/src/{ => extension}/nullable_extension_test.dart (99%) create mode 100644 packages/fpdart/test/src/extension/option_extension_test.dart diff --git a/packages/fpdart/lib/src/extension/date_time_extension.dart b/packages/fpdart/lib/src/extension/date_time_extension.dart index e3fddb44..ec827975 100644 --- a/packages/fpdart/lib/src/extension/date_time_extension.dart +++ b/packages/fpdart/lib/src/extension/date_time_extension.dart @@ -1,7 +1,7 @@ import '../date.dart'; /// `fpdart` extension methods on [DateTime] -extension FpdartDateTime on DateTime { +extension FpdartOnDateTime on DateTime { /// Return `true` when this [DateTime] and `other` have the same **year**. bool eqvYear(DateTime other) => dateEqYear.eqv(this, other); diff --git a/packages/fpdart/lib/src/extension/option_extension.dart b/packages/fpdart/lib/src/extension/option_extension.dart index 6e63d0fd..869f0827 100644 --- a/packages/fpdart/lib/src/extension/option_extension.dart +++ b/packages/fpdart/lib/src/extension/option_extension.dart @@ -2,7 +2,7 @@ import '../function.dart'; import '../option.dart'; import '../typeclass/eq.dart'; -extension OptionExtension on Option { +extension FpdartOnOption on Option { /// Return the current [Option] if it is a [Some], otherwise return the result of `orElse`. /// /// Used to provide an **alt**ernative [Option] in case the current one is [None]. diff --git a/packages/fpdart/test/src/date_test.dart b/packages/fpdart/test/src/date_test.dart index 4c8efe1f..c45da427 100644 --- a/packages/fpdart/test/src/date_test.dart +++ b/packages/fpdart/test/src/date_test.dart @@ -51,23 +51,6 @@ void main() { expect(compare, d1.year == d2.year && d1.month == d2.month && d1.day == d2.day); }); - - Glados2().test('eqvYear == dateEqYear', (d1, d2) { - expect(d1.eqvYear(d2), dateEqYear.eqv(d1, d2)); - }); - - Glados2().test('eqvMonth == dateEqMonth', (d1, d2) { - expect(d1.eqvMonth(d2), dateEqMonth.eqv(d1, d2)); - }); - - Glados2().test('eqvDay == dateEqDay', (d1, d2) { - expect(d1.eqvDay(d2), dateEqDay.eqv(d1, d2)); - }); - - Glados2().test('eqvYearMonthDay == dateEqYear', - (d1, d2) { - expect(d1.eqvYearMonthDay(d2), dateEqYear.eqv(d1, d2)); - }); }); test('dateOrder', () { @@ -120,45 +103,5 @@ void main() { expect(dateEqYearMonthDay.eqv(date1, date2), true); expect(dateEqYearMonthDay.eqv(date1, date3), false); }); - - test('eqvYear', () { - final date1 = DateTime(2021, 2, 2); - final date2 = DateTime(2021, 3, 3); - final date3 = DateTime(2020, 2, 2); - - expect(date1.eqvYear(date1), true); - expect(date1.eqvYear(date2), true); - expect(date1.eqvYear(date3), false); - }); - - test('eqvMonth', () { - final date1 = DateTime(2021, 2, 2); - final date2 = DateTime(2021, 3, 3); - final date3 = DateTime(2020, 2, 2); - - expect(date1.eqvMonth(date1), true); - expect(date1.eqvMonth(date2), false); - expect(date1.eqvMonth(date3), true); - }); - - test('eqvDay', () { - final date1 = DateTime(2021, 2, 2); - final date2 = DateTime(2021, 3, 3); - final date3 = DateTime(2020, 3, 2); - - expect(date1.eqvDay(date1), true); - expect(date1.eqvDay(date2), false); - expect(date1.eqvDay(date3), true); - }); - - test('eqvYearMonthDay', () { - final date1 = DateTime(2021, 2, 2, 10, 10); - final date2 = DateTime(2021, 2, 2, 11, 11); - final date3 = DateTime(2020, 2, 2, 12, 12); - - expect(date1.eqvYearMonthDay(date1), true); - expect(date1.eqvYearMonthDay(date2), true); - expect(date1.eqvYearMonthDay(date3), false); - }); }); } diff --git a/packages/fpdart/test/src/extension/date_time_extension_test.dart b/packages/fpdart/test/src/extension/date_time_extension_test.dart new file mode 100644 index 00000000..049272ca --- /dev/null +++ b/packages/fpdart/test/src/extension/date_time_extension_test.dart @@ -0,0 +1,66 @@ +import 'package:fpdart/fpdart.dart'; + +import '../utils/utils.dart'; + +void main() { + group('FpdartOnDateTime', () { + group('[Property-based testing]', () { + Glados2().test('eqvYear == dateEqYear', (d1, d2) { + expect(d1.eqvYear(d2), dateEqYear.eqv(d1, d2)); + }); + + Glados2().test('eqvMonth == dateEqMonth', (d1, d2) { + expect(d1.eqvMonth(d2), dateEqMonth.eqv(d1, d2)); + }); + + Glados2().test('eqvDay == dateEqDay', (d1, d2) { + expect(d1.eqvDay(d2), dateEqDay.eqv(d1, d2)); + }); + + Glados2().test('eqvYearMonthDay == dateEqYear', + (d1, d2) { + expect(d1.eqvYearMonthDay(d2), dateEqYear.eqv(d1, d2)); + }); + }); + + test('eqvYear', () { + final date1 = DateTime(2021, 2, 2); + final date2 = DateTime(2021, 3, 3); + final date3 = DateTime(2020, 2, 2); + + expect(date1.eqvYear(date1), true); + expect(date1.eqvYear(date2), true); + expect(date1.eqvYear(date3), false); + }); + + test('eqvMonth', () { + final date1 = DateTime(2021, 2, 2); + final date2 = DateTime(2021, 3, 3); + final date3 = DateTime(2020, 2, 2); + + expect(date1.eqvMonth(date1), true); + expect(date1.eqvMonth(date2), false); + expect(date1.eqvMonth(date3), true); + }); + + test('eqvDay', () { + final date1 = DateTime(2021, 2, 2); + final date2 = DateTime(2021, 3, 3); + final date3 = DateTime(2020, 3, 2); + + expect(date1.eqvDay(date1), true); + expect(date1.eqvDay(date2), false); + expect(date1.eqvDay(date3), true); + }); + + test('eqvYearMonthDay', () { + final date1 = DateTime(2021, 2, 2, 10, 10); + final date2 = DateTime(2021, 2, 2, 11, 11); + final date3 = DateTime(2020, 2, 2, 12, 12); + + expect(date1.eqvYearMonthDay(date1), true); + expect(date1.eqvYearMonthDay(date2), true); + expect(date1.eqvYearMonthDay(date3), false); + }); + }); +} diff --git a/packages/fpdart/test/src/list_extension_test.dart b/packages/fpdart/test/src/extension/list_extension_test.dart similarity index 99% rename from packages/fpdart/test/src/list_extension_test.dart rename to packages/fpdart/test/src/extension/list_extension_test.dart index c418bc22..82a389b5 100644 --- a/packages/fpdart/test/src/list_extension_test.dart +++ b/packages/fpdart/test/src/extension/list_extension_test.dart @@ -1,6 +1,6 @@ import 'package:fpdart/fpdart.dart'; -import './utils/utils.dart'; +import '../utils/utils.dart'; /// Used to test sorting with [DateTime] (`sortWithDate`) class SortDate { diff --git a/packages/fpdart/test/src/map_extension_test.dart b/packages/fpdart/test/src/extension/map_extension_test.dart similarity index 99% rename from packages/fpdart/test/src/map_extension_test.dart rename to packages/fpdart/test/src/extension/map_extension_test.dart index ce9f2b76..89b1d890 100644 --- a/packages/fpdart/test/src/map_extension_test.dart +++ b/packages/fpdart/test/src/extension/map_extension_test.dart @@ -1,6 +1,6 @@ import 'package:fpdart/fpdart.dart'; -import './utils/utils.dart'; +import '../utils/utils.dart'; void main() { group('FpdartOnMutableMap', () { diff --git a/packages/fpdart/test/src/nullable_extension_test.dart b/packages/fpdart/test/src/extension/nullable_extension_test.dart similarity index 99% rename from packages/fpdart/test/src/nullable_extension_test.dart rename to packages/fpdart/test/src/extension/nullable_extension_test.dart index 48c3d012..984f910e 100644 --- a/packages/fpdart/test/src/nullable_extension_test.dart +++ b/packages/fpdart/test/src/extension/nullable_extension_test.dart @@ -1,6 +1,6 @@ import 'package:fpdart/fpdart.dart'; -import 'utils/utils.dart'; +import '../utils/utils.dart'; void main() { group("FpdartOnNullable", () { diff --git a/packages/fpdart/test/src/extension/option_extension_test.dart b/packages/fpdart/test/src/extension/option_extension_test.dart new file mode 100644 index 00000000..17344938 --- /dev/null +++ b/packages/fpdart/test/src/extension/option_extension_test.dart @@ -0,0 +1,44 @@ +import 'package:fpdart/fpdart.dart'; + +import '../utils/utils.dart'; + +void main() { + group('FpdartOnOption', () { + group('alt', () { + test('Some', () { + final option = Option.of(10); + final value = option.alt(() => Option.of(0)); + value.matchTestSome((some) => expect(some, 10)); + }); + + test('None', () { + final option = Option.none(); + final value = option.alt(() => Option.of(0)); + value.matchTestSome((some) => expect(some, 0)); + }); + }); + + group('getOrElse', () { + test('Some', () { + final option = Option.of(10); + final value = option.getOrElse(() => 0); + expect(value, 10); + }); + + test('None', () { + final option = Option.none(); + final value = option.getOrElse(() => 0); + expect(value, 0); + }); + }); + + test('elem', () { + final m1 = Option.of(10); + final m2 = Option.none(); + final eq = Eq.instance((a1, a2) => a1 == a2); + expect(m1.elem(10, eq), true); + expect(m1.elem(9, eq), false); + expect(m2.elem(10, eq), false); + }); + }); +} diff --git a/packages/fpdart/test/src/option_test.dart b/packages/fpdart/test/src/option_test.dart index 76a853e6..a28dd43d 100644 --- a/packages/fpdart/test/src/option_test.dart +++ b/packages/fpdart/test/src/option_test.dart @@ -164,34 +164,6 @@ void main() { }); }); - group('getOrElse', () { - test('Some', () { - final option = Option.of(10); - final value = option.getOrElse(() => 0); - expect(value, 10); - }); - - test('None', () { - final option = Option.none(); - final value = option.getOrElse(() => 0); - expect(value, 0); - }); - }); - - group('alt', () { - test('Some', () { - final option = Option.of(10); - final value = option.alt(() => Option.of(0)); - value.matchTestSome((some) => expect(some, 10)); - }); - - test('None', () { - final option = Option.none(); - final value = option.alt(() => Option.of(0)); - value.matchTestSome((some) => expect(some, 0)); - }); - }); - group('extend', () { test('Some', () { final option = Option.of(10); @@ -509,15 +481,6 @@ void main() { expect(m2.fold(() => 'none', (some) => 'some'), 'none'); }); - test('elem', () { - final m1 = Option.of(10); - final m2 = Option.none(); - final eq = Eq.instance((a1, a2) => a1 == a2); - expect(m1.elem(10, eq), true); - expect(m1.elem(9, eq), false); - expect(m2.elem(10, eq), false); - }); - test('none()', () { final m = Option.none(); expect(m, isA()); From d65518f057f45aaa87b9aa3a744f5fe8662eacd0 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 17:33:11 +0200 Subject: [PATCH 14/71] curry extension tests --- packages/fpdart/CHANGELOG.md | 10 +++ .../lib/src/extension/curry_extension.dart | 8 +- .../src/extension/curry_extension_test.dart | 73 +++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 packages/fpdart/test/src/extension/curry_extension_test.dart diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 6adc6b58..21453be2 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -40,6 +40,16 @@ final result = toIntOption("0xFF"); /// `Some(255)` final result = toDoubleOption("10.5"); /// `Some(10.5)` final result = toBoolEither(() => "left")("NO"); /// `Left("left")` ``` +- Changed `uncurry` extension to getter function ⚠️ +```dart +int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; + +/// Before +subtractCurried.uncurry()(10, 5); + +/// New +subtractCurried.uncurry(10, 5); +``` - Removed `bool` extension (`match` and `fold`), use the ternary operator or pattern matching instead ⚠️ ```dart final boolValue = Random().nextBool(); diff --git a/packages/fpdart/lib/src/extension/curry_extension.dart b/packages/fpdart/lib/src/extension/curry_extension.dart index 88d97d94..a5995d33 100644 --- a/packages/fpdart/lib/src/extension/curry_extension.dart +++ b/packages/fpdart/lib/src/extension/curry_extension.dart @@ -14,7 +14,7 @@ extension UncurryExtension2 on Output Function(Input2) /// accepting two parameters. /// /// Inverse of `curry`. - Output Function(Input1, Input2) uncurry() => + Output Function(Input1, Input2) get uncurry => (Input1 input1, Input2 input2) => this(input1)(input2); } @@ -34,7 +34,7 @@ extension UncurryExtension3 /// accepting three parameters. /// /// Inverse of `curry`. - Output Function(Input1, Input2, Input3) uncurry() => + Output Function(Input1, Input2, Input3) get uncurry => (Input1 input1, Input2 input2, Input3 input3) => this(input1)(input2)(input3); } @@ -57,7 +57,7 @@ extension UncurryExtension4 /// accepting four parameters. /// /// Inverse of `curry`. - Output Function(Input1, Input2, Input3, Input4) uncurry() => + Output Function(Input1, Input2, Input3, Input4) get uncurry => (Input1 input1, Input2 input2, Input3 input3, Input4 input4) => this(input1)(input2)(input3)(input4); } @@ -81,7 +81,7 @@ extension UncurryExtension5 /// accepting five parameters. /// /// Inverse of `curry`. - Output Function(Input1, Input2, Input3, Input4, Input5) uncurry() => + Output Function(Input1, Input2, Input3, Input4, Input5) get uncurry => (Input1 input1, Input2 input2, Input3 input3, Input4 input4, Input5 input5) => this(input1)(input2)(input3)(input4)(input5); diff --git a/packages/fpdart/test/src/extension/curry_extension_test.dart b/packages/fpdart/test/src/extension/curry_extension_test.dart new file mode 100644 index 00000000..15631bfb --- /dev/null +++ b/packages/fpdart/test/src/extension/curry_extension_test.dart @@ -0,0 +1,73 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:glados/glados.dart'; + +void main() { + group('Curry/Uncarry extension', () { + group('Curry/Uncarry (2)', () { + int subtract(int n1, int n2) => n1 - n2; + int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; + + Glados2(any.int, any.int).test('curry', (n1, n2) { + expect(subtract(n1, n2), subtract.curry(n1)(n2)); + }); + + Glados2(any.int, any.int).test('uncurry', (n1, n2) { + expect(subtractCurried(n1)(n2), subtractCurried.uncurry(n1, n2)); + }); + }); + + group('Curry/Uncarry (3)', () { + int subtract(int n1, int n2, int n3) => n1 - n2 - n3; + int Function(int) Function(int) subtractCurried(int n1) => + (n2) => (n3) => n1 - n2 - n3; + + Glados3(any.int, any.int, any.int).test('curry', + (n1, n2, n3) { + expect(subtract(n1, n2, n3), subtract.curry(n1)(n2)(n3)); + }); + + Glados3(any.int, any.int, any.int).test('uncurry', + (n1, n2, n3) { + expect( + subtractCurried(n1)(n2)(n3), subtractCurried.uncurry(n1, n2, n3)); + }); + }); + + group('Curry/Uncarry (4)', () { + int subtract(int n1, int n2, int n3, int n4) => n1 - n2 - n3 - n4; + int Function(int) Function(int) Function(int) subtractCurried(int n1) => + (n2) => (n3) => (n4) => n1 - n2 - n3 - n4; + + Glados3(any.int, any.int, any.int).test('curry', + (n1, n2, n3) { + expect(subtract(n1, n2, n3, n1), subtract.curry(n1)(n2)(n3)(n1)); + }); + + Glados3(any.int, any.int, any.int).test('uncurry', + (n1, n2, n3) { + expect(subtractCurried(n1)(n2)(n3)(n1), + subtractCurried.uncurry(n1, n2, n3, n1)); + }); + }); + + group('Curry/Uncarry (5)', () { + int subtract(int n1, int n2, int n3, int n4, int n5) => + n1 - n2 - n3 - n4 - n5; + int Function(int) Function(int) Function(int) Function(int) + subtractCurried(int n1) => + (n2) => (n3) => (n4) => (n5) => n1 - n2 - n3 - n4 - n5; + + Glados3(any.int, any.int, any.int).test('curry', + (n1, n2, n3) { + expect( + subtract(n1, n2, n3, n1, n2), subtract.curry(n1)(n2)(n3)(n1)(n2)); + }); + + Glados3(any.int, any.int, any.int).test('uncurry', + (n1, n2, n3) { + expect(subtractCurried(n1)(n2)(n3)(n1)(n2), + subtractCurried.uncurry(n1, n2, n3, n1, n2)); + }); + }); + }); +} From b8532c4b9dfa3834f05dd11ffc190704605e1c95 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 17:37:52 +0200 Subject: [PATCH 15/71] curry to getter function --- packages/fpdart/CHANGELOG.md | 2 +- .../lib/src/extension/curry_extension.dart | 25 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 21453be2..54db6746 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -40,7 +40,7 @@ final result = toIntOption("0xFF"); /// `Some(255)` final result = toDoubleOption("10.5"); /// `Some(10.5)` final result = toBoolEither(() => "left")("NO"); /// `Left("left")` ``` -- Changed `uncurry` extension to getter function ⚠️ +- Changed `uncurry` and `curry` extension to getter function ⚠️ ```dart int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; diff --git a/packages/fpdart/lib/src/extension/curry_extension.dart b/packages/fpdart/lib/src/extension/curry_extension.dart index a5995d33..a3d0f76a 100644 --- a/packages/fpdart/lib/src/extension/curry_extension.dart +++ b/packages/fpdart/lib/src/extension/curry_extension.dart @@ -4,8 +4,8 @@ extension CurryExtension2 on Output Function( /// that returns another function both accepting one parameter. /// /// Inverse of `uncurry`. - Output Function(Input2) curry(Input1 input1) => - (input2) => this(input1, input2); + Output Function(Input2) Function(Input1) get curry => + (input1) => (input2) => this(input1, input2); } extension UncurryExtension2 on Output Function(Input2) @@ -15,7 +15,7 @@ extension UncurryExtension2 on Output Function(Input2) /// /// Inverse of `curry`. Output Function(Input1, Input2) get uncurry => - (Input1 input1, Input2 input2) => this(input1)(input2); + (input1, input2) => this(input1)(input2); } extension CurryExtension3 on Output Function( @@ -24,8 +24,8 @@ extension CurryExtension3 on Output Function( /// of functions that all accept one parameter. /// /// Inverse of `uncurry`. - Output Function(Input3) Function(Input2) curry(Input1 input1) => - (input2) => (input3) => this(input1, input2, input3); + Output Function(Input3) Function(Input2) Function(Input1) get curry => + (input1) => (input2) => (input3) => this(input1, input2, input3); } extension UncurryExtension3 @@ -35,8 +35,7 @@ extension UncurryExtension3 /// /// Inverse of `curry`. Output Function(Input1, Input2, Input3) get uncurry => - (Input1 input1, Input2 input2, Input3 input3) => - this(input1)(input2)(input3); + (input1, input2, input3) => this(input1)(input2)(input3); } extension CurryExtension4 on Output @@ -45,9 +44,9 @@ extension CurryExtension4 on Output /// of functions that all accept one parameter. /// /// Inverse of `uncurry`. - Output Function(Input4) Function(Input3) Function(Input2) curry( - Input1 input1) => - (input2) => (input3) => (input4) => this(input1, input2, input3, input4); + Output Function(Input4) Function(Input3) Function(Input2) Function(Input1) + get curry => (input1) => (input2) => + (input3) => (input4) => this(input1, input2, input3, input4); } extension UncurryExtension4 @@ -69,7 +68,8 @@ extension CurryExtension5 /// /// Inverse of `uncurry`. Output Function(Input5) Function(Input4) Function(Input3) Function(Input2) - curry(Input1 input1) => (input2) => (input3) => + Function(Input1) + get curry => (input1) => (input2) => (input3) => (input4) => (input5) => this(input1, input2, input3, input4, input5); } @@ -82,7 +82,6 @@ extension UncurryExtension5 /// /// Inverse of `curry`. Output Function(Input1, Input2, Input3, Input4, Input5) get uncurry => - (Input1 input1, Input2 input2, Input3 input3, Input4 input4, - Input5 input5) => + (input1, input2, input3, input4, input5) => this(input1)(input2)(input3)(input4)(input5); } From 9dcad8cd41ea3664030f61bb237a60afaba7ea66 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 17:54:09 +0200 Subject: [PATCH 16/71] curry and curryAll extensions --- packages/fpdart/CHANGELOG.md | 5 ++- .../fpdart/example/src/function/curry.dart | 2 +- .../lib/src/extension/curry_extension.dart | 24 +++++++++-- .../src/extension/curry_extension_test.dart | 43 +++++++++++++------ 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 54db6746..aff0c776 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -40,7 +40,10 @@ final result = toIntOption("0xFF"); /// `Some(255)` final result = toDoubleOption("10.5"); /// `Some(10.5)` final result = toBoolEither(() => "left")("NO"); /// `Left("left")` ``` -- Changed `uncurry` and `curry` extension to getter function ⚠️ +- Updated curry / uncarry extensions ⚠️ + - Renamed `curry` to `curryAll` for functions with 3, 4, 5 parameters + - Changed definition of `curry` to curry only the first parameter + - Changed `uncurry` and `curry` extension to getter function ```dart int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; diff --git a/packages/fpdart/example/src/function/curry.dart b/packages/fpdart/example/src/function/curry.dart index 61d412a3..e40bd59f 100644 --- a/packages/fpdart/example/src/function/curry.dart +++ b/packages/fpdart/example/src/function/curry.dart @@ -29,6 +29,6 @@ void main() { print(sumBy2Extension(10)); print(sumBy10Extension(2)); - final fourParamsCurry = sumMultiplyDivide.curry; + final fourParamsCurry = sumMultiplyDivide.curryAll; final fourParamsUncurry = fourParamsCurry.uncurry; } diff --git a/packages/fpdart/lib/src/extension/curry_extension.dart b/packages/fpdart/lib/src/extension/curry_extension.dart index a3d0f76a..43042cb2 100644 --- a/packages/fpdart/lib/src/extension/curry_extension.dart +++ b/packages/fpdart/lib/src/extension/curry_extension.dart @@ -1,3 +1,7 @@ +/// {@template fpdart_curry_extension} +/// Extract first parameter from this function to allow curring. +/// {@endtemplate} + extension CurryExtension2 on Output Function( Input1, Input2) { /// Convert this function from accepting two parameters to a function @@ -24,8 +28,12 @@ extension CurryExtension3 on Output Function( /// of functions that all accept one parameter. /// /// Inverse of `uncurry`. - Output Function(Input3) Function(Input2) Function(Input1) get curry => + Output Function(Input3) Function(Input2) Function(Input1) get curryAll => (input1) => (input2) => (input3) => this(input1, input2, input3); + + /// {@macro fpdart_curry_extension} + Output Function(Input2, Input3) Function(Input1) get curry => + (input1) => (input2, input3) => this(input1, input2, input3); } extension UncurryExtension3 @@ -45,8 +53,13 @@ extension CurryExtension4 on Output /// /// Inverse of `uncurry`. Output Function(Input4) Function(Input3) Function(Input2) Function(Input1) - get curry => (input1) => (input2) => + get curryAll => (input1) => (input2) => (input3) => (input4) => this(input1, input2, input3, input4); + + /// {@macro fpdart_curry_extension} + Output Function(Input2, Input3, Input4) Function(Input1) get curry => + (input1) => + (input2, input3, input4) => this(input1, input2, input3, input4); } extension UncurryExtension4 @@ -69,8 +82,13 @@ extension CurryExtension5 /// Inverse of `uncurry`. Output Function(Input5) Function(Input4) Function(Input3) Function(Input2) Function(Input1) - get curry => (input1) => (input2) => (input3) => + get curryAll => (input1) => (input2) => (input3) => (input4) => (input5) => this(input1, input2, input3, input4, input5); + + /// {@macro fpdart_curry_extension} + Output Function(Input2, Input3, Input4, Input5) Function(Input1) get curry => + (input1) => (input2, input3, input4, input5) => + this(input1, input2, input3, input4, input5); } extension UncurryExtension5 diff --git a/packages/fpdart/test/src/extension/curry_extension_test.dart b/packages/fpdart/test/src/extension/curry_extension_test.dart index 15631bfb..a45d3323 100644 --- a/packages/fpdart/test/src/extension/curry_extension_test.dart +++ b/packages/fpdart/test/src/extension/curry_extension_test.dart @@ -7,7 +7,7 @@ void main() { int subtract(int n1, int n2) => n1 - n2; int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; - Glados2(any.int, any.int).test('curry', (n1, n2) { + Glados2(any.int, any.int).test('curryAll', (n1, n2) { expect(subtract(n1, n2), subtract.curry(n1)(n2)); }); @@ -18,35 +18,46 @@ void main() { group('Curry/Uncarry (3)', () { int subtract(int n1, int n2, int n3) => n1 - n2 - n3; - int Function(int) Function(int) subtractCurried(int n1) => + int Function(int) Function(int) subtractCurriedAll(int n1) => (n2) => (n3) => n1 - n2 - n3; Glados3(any.int, any.int, any.int).test('curry', (n1, n2, n3) { - expect(subtract(n1, n2, n3), subtract.curry(n1)(n2)(n3)); + expect(subtract(n1, n2, n3), subtract.curry(n1)(n2, n3)); + }); + + Glados3(any.int, any.int, any.int).test('curryAll', + (n1, n2, n3) { + expect(subtract(n1, n2, n3), subtract.curryAll(n1)(n2)(n3)); }); Glados3(any.int, any.int, any.int).test('uncurry', (n1, n2, n3) { - expect( - subtractCurried(n1)(n2)(n3), subtractCurried.uncurry(n1, n2, n3)); + expect(subtractCurriedAll(n1)(n2)(n3), + subtractCurriedAll.uncurry(n1, n2, n3)); }); }); group('Curry/Uncarry (4)', () { int subtract(int n1, int n2, int n3, int n4) => n1 - n2 - n3 - n4; - int Function(int) Function(int) Function(int) subtractCurried(int n1) => + int Function(int) Function(int) Function(int) subtractCurriedAll( + int n1) => (n2) => (n3) => (n4) => n1 - n2 - n3 - n4; Glados3(any.int, any.int, any.int).test('curry', (n1, n2, n3) { - expect(subtract(n1, n2, n3, n1), subtract.curry(n1)(n2)(n3)(n1)); + expect(subtract(n1, n2, n3, n1), subtract.curry(n1)(n2, n3, n1)); + }); + + Glados3(any.int, any.int, any.int).test('curryAll', + (n1, n2, n3) { + expect(subtract(n1, n2, n3, n1), subtract.curryAll(n1)(n2)(n3)(n1)); }); Glados3(any.int, any.int, any.int).test('uncurry', (n1, n2, n3) { - expect(subtractCurried(n1)(n2)(n3)(n1), - subtractCurried.uncurry(n1, n2, n3, n1)); + expect(subtractCurriedAll(n1)(n2)(n3)(n1), + subtractCurriedAll.uncurry(n1, n2, n3, n1)); }); }); @@ -54,19 +65,25 @@ void main() { int subtract(int n1, int n2, int n3, int n4, int n5) => n1 - n2 - n3 - n4 - n5; int Function(int) Function(int) Function(int) Function(int) - subtractCurried(int n1) => + subtractCurriedAll(int n1) => (n2) => (n3) => (n4) => (n5) => n1 - n2 - n3 - n4 - n5; Glados3(any.int, any.int, any.int).test('curry', (n1, n2, n3) { expect( - subtract(n1, n2, n3, n1, n2), subtract.curry(n1)(n2)(n3)(n1)(n2)); + subtract(n1, n2, n3, n1, n2), subtract.curry(n1)(n2, n3, n1, n2)); + }); + + Glados3(any.int, any.int, any.int).test('curryAll', + (n1, n2, n3) { + expect(subtract(n1, n2, n3, n1, n2), + subtract.curryAll(n1)(n2)(n3)(n1)(n2)); }); Glados3(any.int, any.int, any.int).test('uncurry', (n1, n2, n3) { - expect(subtractCurried(n1)(n2)(n3)(n1)(n2), - subtractCurried.uncurry(n1, n2, n3, n1, n2)); + expect(subtractCurriedAll(n1)(n2)(n3)(n1)(n2), + subtractCurriedAll.uncurry(n1, n2, n3, n1, n2)); }); }); }); From 0fa8380198e648f284801e60863fee67aeb979a7 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 18:02:42 +0200 Subject: [PATCH 17/71] updated docs for curry --- packages/fpdart/CHANGELOG.md | 17 ++++- .../fpdart/example/src/function/curry.dart | 4 +- packages/fpdart/lib/src/either.dart | 1 + packages/fpdart/lib/src/function.dart | 71 ------------------- packages/fpdart/lib/src/io_ref.dart | 2 +- packages/fpdart/lib/src/tuple.dart | 1 + .../fpdart/lib/src/typeclass/foldable.dart | 1 + packages/fpdart/lib/src/typeclass/monoid.dart | 1 + packages/fpdart/lib/src/typedef.dart | 1 + 9 files changed, 24 insertions(+), 75 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index aff0c776..26de6dcc 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -44,14 +44,26 @@ final result = toBoolEither(() => "left")("NO"); /// `Left("left")` - Renamed `curry` to `curryAll` for functions with 3, 4, 5 parameters - Changed definition of `curry` to curry only the first parameter - Changed `uncurry` and `curry` extension to getter function + - Removed `curry` and `uncurry` as functions (use extension method instead) ```dart int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; /// Before subtractCurried.uncurry()(10, 5); +final addFunction = (int a, int b) => a + b; +final add = curry2(addFunction); + +[1, 2, 3].map(add(1)); // returns [2, 3, 4] + /// New subtractCurried.uncurry(10, 5); + +final addFunction = (int a, int b) => a + b; +final add = addFunction.curry; + +[1, 2, 3].map(add(1)); // returns [2, 3, 4] +[1, 2, 3].map(addFunction.curry(1)); // returns [2, 3, 4] ``` - Removed `bool` extension (`match` and `fold`), use the ternary operator or pattern matching instead ⚠️ ```dart @@ -65,7 +77,10 @@ final result = boolValue.fold(() => -1, () => 1); final result = boolValue ? 1 : -1; final result = switch (boolValue) { true => 1, false => -1 }; ``` -- Organized all extensions inside internal `extension` folder +- Organized all extensions inside internal `extension` folder + +*** + ## v0.6.0 - 6 May 2023 - Do notation [#97](https://github.com/SandroMaglione/fpdart/pull/97) (Special thanks to [@tim-smart](https://github.com/tim-smart) 🎉) diff --git a/packages/fpdart/example/src/function/curry.dart b/packages/fpdart/example/src/function/curry.dart index e40bd59f..c0dd7655 100644 --- a/packages/fpdart/example/src/function/curry.dart +++ b/packages/fpdart/example/src/function/curry.dart @@ -9,14 +9,14 @@ void main() { /// Convert a function with 2 parameters to a function that /// takes the first parameter and returns a function that takes /// the seconds parameter. - final sumCurry = curry2(sum); + final sumCurry = sum.curry; final sumBy2 = sumCurry(2); final sumBy10 = sumCurry(10); print(sumBy2(10)); print(sumBy10(2)); /// Same as above but with 4 parameters. - final sumMultiplyDivideCurry = curry4(sumMultiplyDivide); + final sumMultiplyDivideCurry = sumMultiplyDivide.curryAll; final sumBy5 = sumMultiplyDivideCurry(5); final multiplyBy2 = sumBy5(2); final divideBy3 = multiplyBy2(3); diff --git a/packages/fpdart/lib/src/either.dart b/packages/fpdart/lib/src/either.dart index ccfa399e..e8f4c30d 100644 --- a/packages/fpdart/lib/src/either.dart +++ b/packages/fpdart/lib/src/either.dart @@ -4,6 +4,7 @@ import 'option.dart'; import 'task_either.dart'; import 'tuple.dart'; import 'typeclass/typeclass.export.dart'; +import 'typedef.dart'; /// Return a `Right(r)`. /// diff --git a/packages/fpdart/lib/src/function.dart b/packages/fpdart/lib/src/function.dart index c01ab1cf..fdca2dd3 100644 --- a/packages/fpdart/lib/src/function.dart +++ b/packages/fpdart/lib/src/function.dart @@ -2,8 +2,6 @@ import 'either.dart'; import 'extension/string_extension.dart'; import 'option.dart'; -typedef Endo = A Function(A a); - /// Returns the given `a`. /// /// Same as `id`. @@ -91,75 +89,6 @@ T2 idSecond(T1 t1, T2 t2) => t2; /// ``` A Function(dynamic b) constF(A a) => (dynamic b) => a; -/// Converts a binary function into a unary function that returns a unary function. -/// -/// Enables the use of partial application of functions. -/// This often leads to more concise function declarations. -/// -/// The generic types are in this order: -/// 1. Type of the first function parameter -/// 2. Type of the second function parameter -/// 3. Return type of the function -/// ```dart -/// final addFunction = (int a, int b) => a + b; -/// final add = curry2(addFunction); -/// -/// [1, 2, 3].map(add(1)) // returns [2, 3, 4] -/// ``` -ReturnType Function(SecondParameter second) Function(FirstParameter first) - curry2( - ReturnType Function(FirstParameter first, SecondParameter second) function, -) => - (FirstParameter first) => - (SecondParameter second) => function(first, second); - -/// Converts a ternary function into unary functions. -D Function(C c) Function(B b) Function(A a) curry3( - D Function(A a, B b, C c) function) { - return (A a) => (B b) => (C c) => function(a, b, c); -} - -/// Converts a 4 parameters function into unary functions. -E Function(D d) Function(C c) Function(B b) Function(A a) curry4( - E Function(A a, B b, C c, D d) function) { - return (A a) => (B b) => (C c) => (D d) => function(a, b, c, d); -} - -/// Converts a 5 parameters function into unary functions. -F Function(E e) Function(D d) Function(C c) Function(B b) Function(A a) - curry5(F Function(A a, B b, C c, D d, E e) function) { - return (A a) => (B b) => (C c) => (D d) => (E e) => function(a, b, c, d, e); -} - -/// Converts a unary function that returns a unary function into a binary function. -/// ```dart -/// final function = (int a) => (int b) => a + b; -/// final uncurried = uncurry(function); -/// uncurried(2, 3) // returns 5 -/// ``` -C Function(A a, B b) uncurry2(C Function(B a) Function(A a) function) { - return (A a, B b) => function(a)(b); -} - -/// Converts a series of unary functions into a ternary function. -D Function(A a, B b, C c) uncurry3( - D Function(C c) Function(B b) Function(A a) function) { - return (A a, B b, C c) => function(a)(b)(c); -} - -/// Converts a series of unary functions into a 4 parameters function. -E Function(A a, B b, C c, D d) uncurry4( - E Function(D d) Function(C c) Function(B b) Function(A a) function) { - return (A a, B b, C c, D d) => function(a)(b)(c)(d); -} - -/// Converts a series of unary functions into a 5 parameters function. -F Function(A a, B b, C c, D d, E e) uncurry5( - F Function(E e) Function(D d) Function(C c) Function(B b) Function(A a) - function) { - return (A a, B b, C c, D d, E e) => function(a)(b)(c)(d)(e); -} - /// {@template fpdart_string_extension_to_num_option} /// Convert this [String] to [num], returns [None] for invalid inputs. /// {@endtemplate} diff --git a/packages/fpdart/lib/src/io_ref.dart b/packages/fpdart/lib/src/io_ref.dart index 258e94b2..60103024 100644 --- a/packages/fpdart/lib/src/io_ref.dart +++ b/packages/fpdart/lib/src/io_ref.dart @@ -1,6 +1,6 @@ -import 'function.dart'; import 'io.dart'; import 'typeclass/eq.dart'; +import 'typedef.dart'; import 'unit.dart'; /// Mutable reference in the [IO] monad. diff --git a/packages/fpdart/lib/src/tuple.dart b/packages/fpdart/lib/src/tuple.dart index 7d3ff4a2..47bac0d9 100644 --- a/packages/fpdart/lib/src/tuple.dart +++ b/packages/fpdart/lib/src/tuple.dart @@ -4,6 +4,7 @@ import 'typeclass/foldable.dart'; import 'typeclass/functor.dart'; import 'typeclass/hkt.dart'; import 'typeclass/monoid.dart'; +import 'typedef.dart'; /// Return a `Tuple2(a, b)`. Tuple2 tuple2(A a, B b) => Tuple2(a, b); diff --git a/packages/fpdart/lib/src/typeclass/foldable.dart b/packages/fpdart/lib/src/typeclass/foldable.dart index 899d036f..3e28d587 100644 --- a/packages/fpdart/lib/src/typeclass/foldable.dart +++ b/packages/fpdart/lib/src/typeclass/foldable.dart @@ -1,5 +1,6 @@ import '../function.dart'; import '../tuple.dart'; +import '../typedef.dart'; import 'hkt.dart'; import 'monoid.dart'; diff --git a/packages/fpdart/lib/src/typeclass/monoid.dart b/packages/fpdart/lib/src/typeclass/monoid.dart index 2cc72785..19080fe5 100644 --- a/packages/fpdart/lib/src/typeclass/monoid.dart +++ b/packages/fpdart/lib/src/typeclass/monoid.dart @@ -1,4 +1,5 @@ import '../function.dart'; +import '../typedef.dart'; import 'eq.dart'; import 'semigroup.dart'; diff --git a/packages/fpdart/lib/src/typedef.dart b/packages/fpdart/lib/src/typedef.dart index 5d64bf9f..f9e465b6 100644 --- a/packages/fpdart/lib/src/typedef.dart +++ b/packages/fpdart/lib/src/typedef.dart @@ -1,5 +1,6 @@ import 'tuple.dart'; import 'typeclass/hkt.dart'; +typedef Endo = A Function(A a); typedef Separated = Tuple2, HKT>; typedef Magma = T Function(T x, T y); From 222dd99f5a249824d741b18813dbc725a1c6640b Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 18:12:59 +0200 Subject: [PATCH 18/71] refactor function and test --- packages/fpdart/CHANGELOG.md | 2 + .../fpdart/example/src/function/identity.dart | 2 - packages/fpdart/lib/src/function.dart | 52 ++----------------- packages/fpdart/test/src/function_test.dart | 14 +++++ 4 files changed, 19 insertions(+), 51 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 26de6dcc..20a722a2 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -77,6 +77,8 @@ final result = boolValue.fold(() => -1, () => 1); final result = boolValue ? 1 : -1; final result = switch (boolValue) { true => 1, false => -1 }; ``` +- Removed `id` and `idFuture`, use `identity` and `identityFuture` instead ⚠️ +- Removed `idFirst` and `idSecond` functions ⚠️ - Organized all extensions inside internal `extension` folder *** diff --git a/packages/fpdart/example/src/function/identity.dart b/packages/fpdart/example/src/function/identity.dart index 2642144a..c8e44c26 100644 --- a/packages/fpdart/example/src/function/identity.dart +++ b/packages/fpdart/example/src/function/identity.dart @@ -9,7 +9,6 @@ Future main() async { /// Using `identity`/`id`, the function just returns its input parameter. final withIdentity = either.match(identity, (r) => '$r'); - final withId = either.match(id, (r) => '$r'); /// Using `identityFuture`/`idFuture`, the function just returns its input /// parameter, wrapped in `Future.value`. @@ -17,5 +16,4 @@ Future main() async { identityFuture, (r) async => '$r', ); - final withidFuture = await either.match(idFuture, (r) async => '$r'); } diff --git a/packages/fpdart/lib/src/function.dart b/packages/fpdart/lib/src/function.dart index fdca2dd3..b4473be2 100644 --- a/packages/fpdart/lib/src/function.dart +++ b/packages/fpdart/lib/src/function.dart @@ -4,8 +4,6 @@ import 'option.dart'; /// Returns the given `a`. /// -/// Same as `id`. -/// /// Shortcut function to return the input parameter: /// ```dart /// final either = Either.of(10); @@ -14,30 +12,11 @@ import 'option.dart'; /// /// the input parameter `(l) => l`. /// final noId = either.match((l) => l, (r) => '$r'); /// -/// /// Using `identity`/`id`, the function just returns its input parameter. +/// /// Using `identity`, the function just returns its input parameter. /// final withIdentity = either.match(identity, (r) => '$r'); -/// final withId = either.match(id, (r) => '$r'); /// ``` T identity(T a) => a; -/// Returns the given `a`. -/// -/// Same as `identity`. -/// -/// Shortcut function to return the input parameter: -/// ```dart -/// final either = Either.of(10); -/// -/// /// Without using `identity`, you must write a function to return -/// /// the input parameter `(l) => l`. -/// final noId = either.match((l) => l, (r) => '$r'); -/// -/// /// Using `identity`/`id`, the function just returns its input parameter. -/// final withIdentity = either.match(identity, (r) => '$r'); -/// final withId = either.match(id, (r) => '$r'); -/// ``` -T id(T a) => a; - /// Returns the given `a`, wrapped in `Future.value`. /// /// Same as `idFuture`. @@ -50,36 +29,11 @@ T id(T a) => a; /// /// the input parameter `(l) async => l`. /// final noId = await either.match((l) async => l, (r) async => '$r'); /// -/// /// Using `identityFuture`/`idFuture`, the function just returns its input parameter. +/// /// Using `identityFuture`, the function just returns its input parameter. /// final withIdentityFuture = either.match(identityFuture, (r) async => '$r'); -/// final withIdFuture = await either.match(idFuture, (r) async => '$r'); /// ``` Future identityFuture(T a) => Future.value(a); -/// Returns the given `a`, wrapped in `Future.value`. -/// -/// Same as `identityFuture`. -/// -/// Shortcut function to return the input parameter: -/// ```dart -/// final either = Either.of(10); -/// -/// /// Without using `idFuture`, you must write a function to return -/// /// the input parameter `(l) async => l`. -/// final noId = await either.match((l) async => l, (r) async => '$r'); -/// -/// /// Using `identityFuture`/`idFuture`, the function just returns its input parameter. -/// final withIdentity = either.match(identityFuture, (r) async => '$r'); -/// final withId = await either.match(idFuture, (r) async => '$r'); -/// ``` -Future idFuture(T a) => Future.value(a); - -/// Returns the **first parameter** from a function that takes two parameters as input. -T1 idFirst(T1 t1, T2 t2) => t1; - -/// Returns the **second parameter** from a function that takes two parameters as input. -T2 idSecond(T1 t1, T2 t2) => t2; - /// `constf a` is a unary function which evaluates to `a` for all inputs. /// ```dart /// final c = constF(10); @@ -87,7 +41,7 @@ T2 idSecond(T1 t1, T2 t2) => t2; /// print(c('any')); // -> 10 /// print(c(112.12)); // -> 10 /// ``` -A Function(dynamic b) constF(A a) => (dynamic b) => a; +A Function(dynamic b) constF(A a) => (B b) => a; /// {@template fpdart_string_extension_to_num_option} /// Convert this [String] to [num], returns [None] for invalid inputs. diff --git a/packages/fpdart/test/src/function_test.dart b/packages/fpdart/test/src/function_test.dart index d3c55701..0e9439c8 100644 --- a/packages/fpdart/test/src/function_test.dart +++ b/packages/fpdart/test/src/function_test.dart @@ -4,6 +4,20 @@ import 'package:glados/glados.dart'; void main() { group('function', () { group('[Property-based testing]', () { + Glados(any.letterOrDigits).test('identity', (stringValue) { + expect(identity(stringValue), stringValue); + }); + + Glados(any.letterOrDigits).test('constF', (stringValue) { + final fun = constF(10); + expect(fun(stringValue), 10); + }); + + Glados(any.letterOrDigits).test('identityFuture', (stringValue) async { + final id = await identityFuture(stringValue); + expect(id, stringValue); + }); + Glados(any.letterOrDigits).test('toNumOption (same as extension)', (stringValue) { expect(stringValue.toNumOption, toNumOption(stringValue)); From 01b0f93c728db0eaa4268dd7174a418b2b3cdae8 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 18:40:27 +0200 Subject: [PATCH 19/71] removed nullable extension --- .../src/option/nullable/chain_nullable.dart | 43 ------ .../src/task_option/future_task_option.dart | 5 - .../lib/src/extension/extension.export.dart | 1 - .../lib/src/extension/nullable_extension.dart | 51 ------- .../lib/src/extension/string_extension.dart | 18 +-- .../extension/nullable_extension_test.dart | 137 ------------------ 6 files changed, 9 insertions(+), 246 deletions(-) delete mode 100644 packages/fpdart/example/src/option/nullable/chain_nullable.dart delete mode 100644 packages/fpdart/lib/src/extension/nullable_extension.dart delete mode 100644 packages/fpdart/test/src/extension/nullable_extension_test.dart diff --git a/packages/fpdart/example/src/option/nullable/chain_nullable.dart b/packages/fpdart/example/src/option/nullable/chain_nullable.dart deleted file mode 100644 index 17229ad1..00000000 --- a/packages/fpdart/example/src/option/nullable/chain_nullable.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:math'; - -import 'package:fpdart/fpdart.dart'; - -int? nullable() => Random().nextBool() ? 10 : null; - -void main(List args) { - /// [Option] <-> `int?` - int? value1 = 10.toOption().map((t) => t + 10).toNullable(); - - bool? value2 = value1?.isEven; - - /// `bool?` -> [Either] -> `int?` - int? value3 = value2 - .toEither(() => "Error") - .flatMap((a) => a ? right(10) : left("None")) - .toNullable(); - - /// `int?` -> [Option] - Option value4 = (value3?.abs().round()).toOption().flatMap(Option.of); - - Option value = (10 - .toOption() - .map((t) => t + 10) - .toNullable() - - /// Null safety 🎯 - ?.ceil() - - /// Null safety 🎯 - .isEven - .toEither(() => "Error") - .flatMap((a) => right(10)) - .toNullable() - - /// Null safety 🎯 - ?.abs() - - /// Null safety 🎯 - .round()) - .toOption() - .flatMap(Option.of); -} diff --git a/packages/fpdart/example/src/task_option/future_task_option.dart b/packages/fpdart/example/src/task_option/future_task_option.dart index 830fe73f..62c87326 100644 --- a/packages/fpdart/example/src/task_option/future_task_option.dart +++ b/packages/fpdart/example/src/task_option/future_task_option.dart @@ -12,11 +12,6 @@ final taskOp = TaskOption.flatten( )), ); -/// New API `toOption`: from `Map?` to `Option>` -final taskOpNew = TaskOption>( - () async => (await example).toOption(), -); - /// Using `Option.fromNullable`, the [Future] cannot fail final taskOpNoFail = TaskOption>( () async => Option.fromNullable(await example), diff --git a/packages/fpdart/lib/src/extension/extension.export.dart b/packages/fpdart/lib/src/extension/extension.export.dart index 10e87089..a8cdedb1 100644 --- a/packages/fpdart/lib/src/extension/extension.export.dart +++ b/packages/fpdart/lib/src/extension/extension.export.dart @@ -3,6 +3,5 @@ export 'curry_extension.dart'; export 'date_time_extension.dart'; export 'list_extension.dart'; export 'map_extension.dart'; -export 'nullable_extension.dart'; export 'option_extension.dart'; export 'string_extension.dart'; diff --git a/packages/fpdart/lib/src/extension/nullable_extension.dart b/packages/fpdart/lib/src/extension/nullable_extension.dart deleted file mode 100644 index 8e3d80a9..00000000 --- a/packages/fpdart/lib/src/extension/nullable_extension.dart +++ /dev/null @@ -1,51 +0,0 @@ -import '../either.dart'; -import '../io_either.dart'; -import '../io_option.dart'; -import '../option.dart'; -import '../task.dart'; -import '../task_either.dart'; -import '../task_option.dart'; - -/// `fpdart` extension to work on nullable values `T?` -extension FpdartOnNullable on T? { - /// Convert a nullable type `T?` to [Option]: - /// {@template fpdart_on_nullable_to_option} - /// - [Some] if the value is **not** `null` - /// - [None] if the value is `null` - /// {@endtemplate} - Option toOption() => Option.fromNullable(this); - - /// Convert a nullable type `T?` to [Either]: - /// {@template fpdart_on_nullable_to_either} - /// - [Right] if the value is **not** `null` - /// - [Left] containing the result of `onNull` if the value is `null` - /// {@endtemplate} - Either toEither(L Function() onNull) => - Either.fromNullable(this, onNull); - - /// Convert a nullable type `T?` to [IOOption]: - /// {@macro fpdart_on_nullable_to_option} - IOOption toIOOption() => IOOption.fromNullable(this); - - /// Convert a nullable type `T?` to [TaskOption]: - /// {@macro fpdart_on_nullable_to_option} - TaskOption toTaskOption() => TaskOption.fromNullable(this); - - /// Convert a nullable type `T?` to [IOEither]. - IOEither toIOEither(L Function() onNull) => - IOEither.fromNullable(this, onNull); - - /// Convert a nullable type `T?` to [TaskEither] using a **sync function**: - /// {@macro fpdart_on_nullable_to_either} - /// - /// If you want to run an **async** function `onNull`, use `toTaskEitherAsync`. - TaskEither toTaskEither(L Function() onNull) => - TaskEither.fromNullable(this, onNull); - - /// Convert a nullable type `T?` to [TaskEither] using an **async function**: - /// {@macro fpdart_on_nullable_to_either} - /// - /// If you want to run an **sync** function `onNull`, use `toTaskEither`. - TaskEither toTaskEitherAsync(Task onNull) => - TaskEither.fromNullableAsync(this, onNull); -} diff --git a/packages/fpdart/lib/src/extension/string_extension.dart b/packages/fpdart/lib/src/extension/string_extension.dart index 06ad665e..19cb14ad 100644 --- a/packages/fpdart/lib/src/extension/string_extension.dart +++ b/packages/fpdart/lib/src/extension/string_extension.dart @@ -1,34 +1,34 @@ import '../either.dart'; import '../option.dart'; -import 'nullable_extension.dart'; /// Functional programming functions on dart [String] using `fpdart`. extension FpdartOnString on String { /// {@macro fpdart_string_extension_to_num_option} - Option get toNumOption => num.tryParse(this).toOption(); + Option get toNumOption => Option.fromNullable(num.tryParse(this)); /// {@macro fpdart_string_extension_to_int_option} - Option get toIntOption => int.tryParse(this).toOption(); + Option get toIntOption => Option.fromNullable(int.tryParse(this)); /// {@macro fpdart_string_extension_to_double_option} - Option get toDoubleOption => double.tryParse(this).toOption(); + Option get toDoubleOption => + Option.fromNullable(double.tryParse(this)); /// {@macro fpdart_string_extension_to_bool_option} - Option get toBoolOption => bool.tryParse(this).toOption(); + Option get toBoolOption => Option.fromNullable(bool.tryParse(this)); /// {@macro fpdart_string_extension_to_num_either} Either toNumEither(L Function() onLeft) => - num.tryParse(this).toEither(onLeft); + Either.fromNullable(num.tryParse(this), onLeft); /// {@macro fpdart_string_extension_to_int_either} Either toIntEither(L Function() onLeft) => - int.tryParse(this).toEither(onLeft); + Either.fromNullable(int.tryParse(this), onLeft); /// {@macro fpdart_string_extension_to_double_either} Either toDoubleEither(L Function() onLeft) => - double.tryParse(this).toEither(onLeft); + Either.fromNullable(double.tryParse(this), onLeft); /// {@macro fpdart_string_extension_to_bool_either} Either toBoolEither(L Function() onLeft) => - bool.tryParse(this).toEither(onLeft); + Either.fromNullable(bool.tryParse(this), onLeft); } diff --git a/packages/fpdart/test/src/extension/nullable_extension_test.dart b/packages/fpdart/test/src/extension/nullable_extension_test.dart deleted file mode 100644 index 984f910e..00000000 --- a/packages/fpdart/test/src/extension/nullable_extension_test.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'package:fpdart/fpdart.dart'; - -import '../utils/utils.dart'; - -void main() { - group("FpdartOnNullable", () { - group("toOption", () { - test('Some', () { - final value = 10; - final result = value.toOption(); - result.matchTestSome((t) { - expect(t, value); - }); - }); - - test('None', () { - int? value = null; - final result = value.toOption(); - expect(result, isA()); - }); - }); - - group("toEither", () { - test('Right', () { - final value = 10; - final result = value.toEither(() => "Error"); - result.matchTestRight((t) { - expect(t, value); - }); - }); - - test('Left', () { - int? value = null; - final result = value.toEither(() => "Error"); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - - group("toIOOption", () { - test('Right', () { - final value = 10; - final io = value.toIOOption(); - final result = io.run(); - result.matchTestSome((t) { - expect(t, value); - }); - }); - - test('Left', () { - int? value = null; - final io = value.toIOOption(); - final result = io.run(); - expect(result, isA()); - }); - }); - - group("toTaskOption", () { - test('Right', () async { - final value = 10; - final task = value.toTaskOption(); - final result = await task.run(); - result.matchTestSome((t) { - expect(t, value); - }); - }); - - test('Left', () async { - int? value = null; - final task = value.toTaskOption(); - final result = await task.run(); - expect(result, isA()); - }); - }); - - group("toIOEither", () { - test('Right', () { - final value = 10; - final task = value.toIOEither(() => "Error"); - final result = task.run(); - result.matchTestRight((t) { - expect(t, value); - }); - }); - - test('Left', () { - int? value = null; - final task = value.toIOEither(() => "Error"); - final result = task.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - - group("toTaskEither", () { - test('Right', () async { - final value = 10; - final task = value.toTaskEither(() => "Error"); - final result = await task.run(); - result.matchTestRight((t) { - expect(t, value); - }); - }); - - test('Left', () async { - int? value = null; - final task = value.toTaskEither(() => "Error"); - final result = await task.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - - group("toTaskEitherAsync", () { - test('Right', () async { - final value = 10; - final task = value.toTaskEitherAsync(Task.of("Error")); - final result = await task.run(); - result.matchTestRight((t) { - expect(t, value); - }); - }); - - test('Left', () async { - int? value = null; - final task = value.toTaskEitherAsync(Task.of("Error")); - final result = await task.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - }); -} From 92fdea742fb3bd7ced7cb39445d77b8cddd8567d Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 18:43:18 +0200 Subject: [PATCH 20/71] removed Compose API --- .../fpdart/example/src/compose/overview.dart | 38 --- packages/fpdart/lib/fpdart.dart | 1 - packages/fpdart/lib/src/compose.dart | 265 ------------------ .../lib/src/extension/compose_extension.dart | 15 - .../lib/src/extension/extension.export.dart | 1 - packages/fpdart/test/src/compose_test.dart | 60 ---- 6 files changed, 380 deletions(-) delete mode 100644 packages/fpdart/example/src/compose/overview.dart delete mode 100644 packages/fpdart/lib/src/compose.dart delete mode 100644 packages/fpdart/lib/src/extension/compose_extension.dart delete mode 100644 packages/fpdart/test/src/compose_test.dart diff --git a/packages/fpdart/example/src/compose/overview.dart b/packages/fpdart/example/src/compose/overview.dart deleted file mode 100644 index b0536144..00000000 --- a/packages/fpdart/example/src/compose/overview.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:fpdart/fpdart.dart'; - -int x2(int a) => a * 2; -int sub(int a, int b) => a - b; - -void main() { - /// Compose one input 1️⃣ - final compose1 = const Compose(x2, 2).c1(x2); - final compose1Extension = x2.c(2).c1(x2); - - // 2 * 2 * 2 = 8 - print(compose1()); - print(compose1Extension()); - - /// Compose two inputs 2️⃣ - final compose2 = const Compose2(sub, 2, 4).c2(sub, 2); - final compose2Extension = sub.c(2, 4).c2(sub, 2); - - // ((2 - 4) - 2) = ((-2) - 2) = -4 - print(compose2()); - print(compose2Extension()); - - /// Compose one input and two inputs in a chain 1️⃣👉2️⃣ - final compose1_2 = x2.c(2).c2(sub, 2).c1(x2); - - // ((2 * 2) - 2) * 2 = (4 - 2) * 2 = (2) * 2 = 4 - print(compose1_2()); - - /// Compose operator ➕ - final composeOperator = x2.c(2) * x2.c; - final composeOperator2 = sub.c(4, 2) * sub.c; - - // ((2 * 2) * 2) = (2 * 2) = 8 - print(composeOperator()); - - // ((4 - 2) - 2) = (2 - 2) = 0 - print(composeOperator2()); -} diff --git a/packages/fpdart/lib/fpdart.dart b/packages/fpdart/lib/fpdart.dart index f18f83ec..b121965b 100644 --- a/packages/fpdart/lib/fpdart.dart +++ b/packages/fpdart/lib/fpdart.dart @@ -1,4 +1,3 @@ -export 'src/compose.dart'; export 'src/date.dart'; export 'src/either.dart'; export 'src/extension/extension.export.dart'; diff --git a/packages/fpdart/lib/src/compose.dart b/packages/fpdart/lib/src/compose.dart deleted file mode 100644 index b707678b..00000000 --- a/packages/fpdart/lib/src/compose.dart +++ /dev/null @@ -1,265 +0,0 @@ -// ignore_for_file: library_private_types_in_public_api - -/// Compose a series of functions, the output of the previous function in the chain -/// is automatically passed as input to the next function in the chain. -///```dart -/// int x2(int a) => a * 2; -/// -/// /// Compose one input 1️⃣ -/// final compose1 = const Compose(x2, 2).c1(x2); -///``` -/// Used to compose functions with **only one input** of type `Input`. -/// If the function has more than one input, use [Compose2], [Compose3] (coming soon), etc. - -class Compose { - final Input _input; - final Output Function(Input input) _compose; - - /// Instance of [Compose] given the function `Output Function(Input input)` to execute - /// and the `Input` to pass to the function. - const Compose(this._compose, this._input); - - /// Chain a function that takes the output `Output` from this [Compose] and - /// returns a new [Compose] that when called will execute `c1Compose`, - /// returning a value of type `ComposedOutput`. - Compose c1( - ComposedOutput Function(Output output) c1Compose, - ) => - Compose( - (input) => c1Compose(_compose(input)), - _input, - ); - - /// Chain a function that takes two inputs: - /// 1. The output `Output` from this [Compose] - /// 2. A new input of type `ComposedInput` - /// - /// It returns a two-input composable object that when called will execute `c2Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose12 - c2( - ComposedOutput Function(Output output, ComposedInput composedInput) - c2Compose, - ComposedInput c2Input, - ) => - _ChainCompose12(c2Compose, _compose, _input, c2Input); - - /// Chain two [Compose]. The second [Compose] must have the same - /// `Input` and `Output` of the first. - /// ```dart - /// int x2(int a) => a * 2; - /// - /// /// Compose operator ➕ - /// final composeOperator = x2.c(2) * x2.c; - /// - /// // ((2 * 2) * 2) = (2 * 2) = 8 - /// print(composeOperator()); - /// ``` - Compose operator *( - Compose Function(Output output) other, - ) => - c1((output) => other(output)()); - - /// Compute the result of the chain. - Output call() => _compose(_input); -} - -/// Compose a series of functions, the output of the previous function in the chain -/// is automatically passed as input to the next function in the chain. -///```dart -/// int sub(int a, int b) => a - b; -/// -/// /// Compose two inputs 2️⃣ -/// final compose2 = const Compose2(sub, 2, 4).c2(sub, 2); -///``` -/// Used to compose functions with **two inputs** of type `Input1` and `Input2`. -/// If the function has a different number of inputs, use [Compose1], [Compose3] (coming soon), etc. -class Compose2 { - final Input1 _input1; - final Input2 _input2; - final Output Function(Input1 input1, Input2 input2) _compose; - - /// Instance of [Compose2] given the function `Output Function(Input1 input1, Input2 input2)` - /// to execute and the `Input1` and `Input2` to pass to the function. - const Compose2(this._compose, this._input1, this._input2); - - /// Chain a function that takes the output `Output` from this [Compose2] and - /// returns a new [Compose] that when called will execute `c1Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose21 c1( - ComposedOutput Function(Output output) c1Compose, - ) => - _ChainCompose21(c1Compose, _compose, _input1, _input2); - - /// Chain a function that takes two inputs: - /// 1. The output `Output` from this [Compose2] - /// 2. A new input of type `ComposedInput` - /// - /// It returns a two-input composable object that when called will execute `c2Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose22 - c2( - ComposedOutput Function(Output output, ComposedInput c2input) c2Compose, - ComposedInput composedInput, - ) => - _ChainCompose22(c2Compose, _compose, _input1, _input2, composedInput); - - /// Chain two [Compose2]. The second operand must have inputs of type - /// `Output` and `Input2` and must return `Output`. - /// ```dart - /// int sub(int a, int b) => a - b; - /// - /// /// Compose operator ➕ - /// final composeOperator2 = sub.c(4, 2) * sub.c; - /// - /// // ((4 - 2) - 2) = (2 - 2) = 0 - /// print(composeOperator2()); - /// ``` - _ChainCompose22 operator *( - Compose2 Function(Output output, Input2 input2) - other, - ) => - c2((output, input2) => other(output, input2)(), _input2); - - /// Compute the result of the chain. - Output call() => _compose(_input1, _input2); -} - -// --- Composition classes below 👇 --- // - -/// Compose a function that takes 1 parameter with a function that takes 2 parameters -class _ChainCompose12 { - final InputC1 _inputC1; - final InputC2 _inputC2; - final OutputC1 Function(InputC1 inputC1) _composeC1; - final OutputC2 Function(OutputC1 outputC1, InputC2 inputC2) _composeC2; - const _ChainCompose12( - this._composeC2, this._composeC1, this._inputC1, this._inputC2); - - /// Chain a function that takes the output `OutputC2` from this composable and - /// returns a new composable that when called will execute `c1Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose21 c1< - ComposedOutput>( - ComposedOutput Function(OutputC2 outputC2) c1Compose) => - _ChainCompose21(c1Compose, _composeC2, _composeC1(_inputC1), _inputC2); - - /// Chain a function that takes two inputs: - /// 1. The output `OutputC2` from this composable - /// 2. A new input of type `ComposedInput` - /// - /// It returns a two-input composable object that when called will execute `c2Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose22 - c2( - ComposedOutput Function(OutputC2 outputC2, ComposedInput composedInput) - c2Compose, - ComposedInput composedInput, - ) => - _ChainCompose22(c2Compose, _composeC2, _composeC1(_inputC1), _inputC2, - composedInput); - - /// Compute the result of the chain. - OutputC2 call() => _composeC2(_composeC1(_inputC1), _inputC2); -} - -/// Compose a function that takes 2 parameters with a function that takes 1 parameter -class _ChainCompose21 { - final Input1C2 _input1c2; - final Input2C2 _input2c2; - final OutputC1 Function(OutputC2 i) _composeC1; - final OutputC2 Function(Input1C2 i1, Input2C2 i2) _composeC2; - const _ChainCompose21( - this._composeC1, this._composeC2, this._input1c2, this._input2c2); - - /// Chain a function that takes the output `OutputC1` from this composable and - /// returns a new composable that when called will execute `c1Compose`, - /// returning a value of type `ComposedOutput`. - Compose c1( - ComposedOutput Function(OutputC1 outputC1) c1Compose, - ) => - Compose(c1Compose, call()); - - /// Chain a function that takes two inputs: - /// 1. The output `OutputC1` from this composable - /// 2. A new input of type `ComposedInput` - /// - /// It returns a two-input composable object that when called will execute `c2Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose12 - c2( - ComposedOutput Function(OutputC1 outputC1, ComposedInput composedInput) - c2Compose, - ComposedInput composedInput, - ) => - _ChainCompose12( - c2Compose, - _composeC1, - _composeC2(_input1c2, _input2c2), - composedInput, - ); - - /// Compute the result of the chain. - OutputC1 call() => _composeC1(_composeC2(_input1c2, _input2c2)); -} - -/// Compose a function that takes 2 parameters with another function that takes 2 parameters -class _ChainCompose22 { - final Input1C2_1 _input1c2_1; - final Input2C2_1 _input2c2_1; - final InputC2_2 _inputC2_2; - final OutputC2_1 Function( - Input1C2_1 input1c2_1, - Input2C2_1 input2c2_1, - ) _composeC2_1; - final OutputC2_2 Function( - OutputC2_1 outputC2_1, - InputC2_2 inputC2_2, - ) _composeC2_2; - const _ChainCompose22( - this._composeC2_2, - this._composeC2_1, - this._input1c2_1, - this._input2c2_1, - this._inputC2_2, - ); - - /// Chain a function that takes the output `Output` from this composable and - /// returns a new composable that when called will execute `c1Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose21 - c1( - ComposedOutput Function(OutputC2_2 outputC2_2) c1Compose, - ) => - _ChainCompose21( - c1Compose, - _composeC2_2, - _composeC2_1(_input1c2_1, _input2c2_1), - _inputC2_2, - ); - - /// Chain a function that takes two inputs: - /// 1. The output `OutputC2_2` from this composable - /// 2. A new input of type `ComposedInput` - /// - /// It returns a two-input composable object that when called will execute `c2Compose`, - /// returning a value of type `ComposedOutput`. - _ChainCompose22 c2( - ComposedOutput Function(OutputC2_2 outputC2_2, ComposedInput composedInput) - c2Compose, - ComposedInput composedInput, - ) => - _ChainCompose22( - c2Compose, - _composeC2_2, - _composeC2_1(_input1c2_1, _input2c2_1), - _inputC2_2, - composedInput, - ); - - /// Compute the result of the chain. - OutputC2_2 call() => - _composeC2_2(_composeC2_1(_input1c2_1, _input2c2_1), _inputC2_2); -} diff --git a/packages/fpdart/lib/src/extension/compose_extension.dart b/packages/fpdart/lib/src/extension/compose_extension.dart deleted file mode 100644 index 34c74df7..00000000 --- a/packages/fpdart/lib/src/extension/compose_extension.dart +++ /dev/null @@ -1,15 +0,0 @@ -import '../compose.dart'; - -extension ComposeOnFunction1 on Output Function(Input) { - /// Build a [Compose] from the function that can be used to - /// easily compose functions in a chain. - Compose c(Input input) => Compose(this, input); -} - -extension ComposeOnFunction2 on Output Function( - Input1, Input2) { - /// Build a [Compose2] from the function that can be used to - /// easily compose functions in a chain. - Compose2 c(Input1 input1, Input2 input2) => - Compose2(this, input1, input2); -} diff --git a/packages/fpdart/lib/src/extension/extension.export.dart b/packages/fpdart/lib/src/extension/extension.export.dart index a8cdedb1..4f1a2c85 100644 --- a/packages/fpdart/lib/src/extension/extension.export.dart +++ b/packages/fpdart/lib/src/extension/extension.export.dart @@ -1,4 +1,3 @@ -export 'compose_extension.dart'; export 'curry_extension.dart'; export 'date_time_extension.dart'; export 'list_extension.dart'; diff --git a/packages/fpdart/test/src/compose_test.dart b/packages/fpdart/test/src/compose_test.dart deleted file mode 100644 index 653c1124..00000000 --- a/packages/fpdart/test/src/compose_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:fpdart/fpdart.dart'; -import 'package:test/test.dart'; - -void main() { - int x2(int a) => a * 2; - int sub(int a, int b) => a - b; - - double div(int a) => a / 2; - String full(int a, double b) => '$a-$b'; - - group('Compose', () { - test('call', () { - final c = Compose(x2, 2); - final c1 = Compose(div, 10); - expect(c(), 4); - expect(c1(), 5.0); - }); - - test('c1', () { - final c = Compose(x2, 2).c1(x2); - final c1 = Compose(x2, 2).c1(div); - expect(c(), 8); - expect(c1(), 2); - }); - - test('c2', () { - final c = Compose(x2, 2).c2(sub, 1); - expect(c(), 3); - }); - - test('*', () { - final c = Compose(x2, 2) * x2.c; - expect(c(), 8); - }); - }); - - group('Compose2', () { - test('call', () { - final c = Compose2(sub, 4, 2); - final c1 = Compose2(full, 4, 2.0); - expect(c(), 2); - expect(c1(), '4-2.0'); - }); - - test('c1', () { - final c = Compose2(sub, 4, 2).c1(x2); - expect(c(), 4); - }); - - test('c2', () { - final c = Compose2(sub, 4, 2).c2(sub, 1); - expect(c(), 1); - }); - - test('*', () { - final c = Compose2(sub, 4, 2) * sub.c; - expect(c(), 0); - }); - }); -} From 00ad525cadd5af34aa535851156801bbd8673a88 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 18:52:53 +0200 Subject: [PATCH 21/71] updated CHANGELOG --- packages/fpdart/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 20a722a2..440320de 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -79,6 +79,8 @@ final result = switch (boolValue) { true => 1, false => -1 }; ``` - Removed `id` and `idFuture`, use `identity` and `identityFuture` instead ⚠️ - Removed `idFirst` and `idSecond` functions ⚠️ +- Removed `Compose` class and extension methods ⚠️ +- Removed extension methods on nullable types (`toOption`, `toEither`, `toTaskOption`, `toIOEither`, `toTaskEither`, `toTaskEitherAsync`) ⚠️ - Organized all extensions inside internal `extension` folder *** From 286a0eb36c4af58aa20fdea5ab778584700a2ee8 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 19:01:57 +0200 Subject: [PATCH 22/71] functions as getters --- packages/fpdart/CHANGELOG.md | 12 ++++++++++++ .../example/src/either/shopping/functional.dart | 2 +- .../example/src/option/shopping/functional.dart | 2 +- packages/fpdart/lib/src/date.dart | 4 ++-- packages/fpdart/lib/src/random.dart | 4 ++-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 440320de..72232fe9 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -40,6 +40,18 @@ final result = toIntOption("0xFF"); /// `Some(255)` final result = toDoubleOption("10.5"); /// `Some(10.5)` final result = toBoolEither(() => "left")("NO"); /// `Left("left")` ``` +- Changed `dateNow`, `now`, `random`, and `randomBool` to getter functions +```dart +/// Before +Option getRandomOption(T value) => randomBool() + .map((isValid) => isValid ? some(value) : none()) + .run(); + +/// Now +Option getRandomOption(T value) => randomBool + .map((isValid) => isValid ? some(value) : none()) + .run(); +``` - Updated curry / uncarry extensions ⚠️ - Renamed `curry` to `curryAll` for functions with 3, 4, 5 parameters - Changed definition of `curry` to curry only the first parameter diff --git a/packages/fpdart/example/src/either/shopping/functional.dart b/packages/fpdart/example/src/either/shopping/functional.dart index ce071f36..2b22d4cd 100644 --- a/packages/fpdart/example/src/either/shopping/functional.dart +++ b/packages/fpdart/example/src/either/shopping/functional.dart @@ -13,7 +13,7 @@ class Market { getRandomEither(randomInt(1, 10).run(), "Empty 💁🏼‍♂️"); } -Either getRandomEither(R right, L left) => randomBool() +Either getRandomEither(R right, L left) => randomBool .map>( (isValid) => isValid ? Either.of(right) : Either.left(left), ) diff --git a/packages/fpdart/example/src/option/shopping/functional.dart b/packages/fpdart/example/src/option/shopping/functional.dart index 6b5eb9d7..a8146c60 100644 --- a/packages/fpdart/example/src/option/shopping/functional.dart +++ b/packages/fpdart/example/src/option/shopping/functional.dart @@ -12,7 +12,7 @@ class Market { Option buyAmount() => getRandomOption(randomInt(1, 10).run()); } -Option getRandomOption(T value) => randomBool() +Option getRandomOption(T value) => randomBool .map( (isValid) => isValid ? some(value) : none(), ) diff --git a/packages/fpdart/lib/src/date.dart b/packages/fpdart/lib/src/date.dart index 335a5f9c..068cc7fa 100644 --- a/packages/fpdart/lib/src/date.dart +++ b/packages/fpdart/lib/src/date.dart @@ -5,14 +5,14 @@ import 'typeclass/order.dart'; /// Constructs a [DateTime] instance with current date and time in the local time zone. /// /// [IO] wrapper around dart `DateTime.now()`. -IO dateNow() => IO(() => DateTime.now()); +IO get dateNow => IO(() => DateTime.now()); /// The number of milliseconds since the "Unix epoch" 1970-01-01T00:00:00Z (UTC). /// /// This value is independent of the time zone. /// /// [IO] wrapper around dart `DateTime.now().millisecondsSinceEpoch`. -IO now() => dateNow().map((date) => date.millisecondsSinceEpoch); +IO get now => dateNow.map((date) => date.millisecondsSinceEpoch); /// [Order] instance on dart [DateTime]. final Order dateOrder = Order.from( diff --git a/packages/fpdart/lib/src/random.dart b/packages/fpdart/lib/src/random.dart index be640c42..ccebc799 100644 --- a/packages/fpdart/lib/src/random.dart +++ b/packages/fpdart/lib/src/random.dart @@ -6,12 +6,12 @@ import 'io.dart'; /// value uniformly distributed in the range from 0.0, **inclusive**, to 1.0, **exclusive**. /// /// [IO] wrapper around dart `Random().nextDouble()`. -IO random() => IO(() => Random().nextDouble()); +IO get random => IO(() => Random().nextDouble()); /// Generates a random boolean value. /// /// [IO] wrapper around dart `Random().nextBool()`. -IO randomBool() => IO(() => Random().nextBool()); +IO get randomBool => IO(() => Random().nextBool()); /// Generates a non-negative random integer uniformly distributed /// in the range from `min`, **inclusive**, to `max`, **exclusive**. From 9e2303bfa3d2c51a3fe9bf167a57503f7ceab3db Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 19:06:46 +0200 Subject: [PATCH 23/71] remove tuple --- packages/fpdart/lib/fpdart.dart | 1 - packages/fpdart/lib/src/tuple.dart | 194 --------------------- packages/fpdart/test/src/tuple_test.dart | 209 ----------------------- 3 files changed, 404 deletions(-) delete mode 100644 packages/fpdart/lib/src/tuple.dart delete mode 100644 packages/fpdart/test/src/tuple_test.dart diff --git a/packages/fpdart/lib/fpdart.dart b/packages/fpdart/lib/fpdart.dart index b121965b..963f9d92 100644 --- a/packages/fpdart/lib/fpdart.dart +++ b/packages/fpdart/lib/fpdart.dart @@ -15,7 +15,6 @@ export 'src/state_async.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'; export 'src/unit.dart'; diff --git a/packages/fpdart/lib/src/tuple.dart b/packages/fpdart/lib/src/tuple.dart deleted file mode 100644 index 47bac0d9..00000000 --- a/packages/fpdart/lib/src/tuple.dart +++ /dev/null @@ -1,194 +0,0 @@ -import 'function.dart'; -import 'typeclass/extend.dart'; -import 'typeclass/foldable.dart'; -import 'typeclass/functor.dart'; -import 'typeclass/hkt.dart'; -import 'typeclass/monoid.dart'; -import 'typedef.dart'; - -/// Return a `Tuple2(a, b)`. -Tuple2 tuple2(A a, B b) => Tuple2(a, b); - -/// Build a `Tuple2(a, b)` using a curried function in which the first function -/// accepts `a` and the second function accepts `b`. -Tuple2 Function(B b) tuple2CurriedFirst(A a) => - (B b) => Tuple2(a, b); - -/// Build a `Tuple2(a, b)` using a curried function in which the first function -/// accepts `b` and the second function accepts `a`. -Tuple2 Function(A a) tuple2CurriedSecond(B b) => - (A a) => Tuple2(a, b); - -/// Tag the [HKT2] interface for the actual [Tuple2]. -abstract class _Tuple2HKT {} - -/// `Tuple2` is a type that contains a value of type `T1` and a value of type `T2`. -/// -/// Used to avoid creating a custom class that contains two different types -/// or to return two values from one function. -class Tuple2 extends HKT2<_Tuple2HKT, T1, T2> - with - Functor2<_Tuple2HKT, T1, T2>, - Extend2<_Tuple2HKT, T1, T2>, - Foldable2<_Tuple2HKT, T1, T2> { - final T1 _value1; - final T2 _value2; - - /// Build a [Tuple2] given its first and second values. - const Tuple2(this._value1, this._value2); - - /// Get first value inside the [Tuple]. - T1 get first => _value1; - - /// Get second value inside the [Tuple]. - T2 get second => _value2; - - /// Return result of calling `f` given `value1` and `value2` from this [Tuple]. - A apply(A Function(T1 first, T2 second) f) => f(_value1, _value2); - - /// Change type of first value of the [Tuple] from `T1` to `TN` using `f`. - Tuple2 mapFirst(TN Function(T1 first) f) => - Tuple2(f(_value1), _value2); - - /// Change type of second value of the [Tuple] from `T2` to `TN` using `f`. - Tuple2 mapSecond(TN Function(T2 second) f) => - Tuple2(_value1, f(_value2)); - - /// Change type of both values of the [Tuple] using `f`. - /// - /// This is the same as `mapFirst` and `mapSecond` combined. - Tuple2 mapBoth(Tuple2 Function(T1 first, T2 second) f) => - f(_value1, _value2); - - /// Change type of second value of the [Tuple] from `T2` to `C` using `f`. - /// - /// Same as `mapSecond`. - @override - Tuple2 map(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 bimap(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 extend(Z Function(Tuple2 t) f) => - Tuple2(_value1, f(this)); - - /// Wrap this [Tuple2] inside another [Tuple2]. - @override - Tuple2> duplicate() => extend(identity); - - /// Convert the first value of the [Tuple2] from `T1` to `Z` using `f`. - Tuple2 extendFirst(Z Function(Tuple2 t) f) => - Tuple2(f(this), _value2); - - /// Return value of type `C` by calling `f` with `b` and the second value of the [Tuple2]. - /// - /// Same as `foldLeft`. - @override - C foldRight(C b, C Function(C acc, T2 a) f) => f(b, _value2); - - /// Return value of type `C` by calling `f` with `b` and the first value of the [Tuple2]. - /// - /// Same as `foldLeftFirst`. - C foldRightFirst(C b, C Function(C acc, T1 a) f) => f(b, _value1); - - /// Return value of type `C` by calling `f` with `b` and the second value of the [Tuple2]. - /// - /// Same as `foldRight`. - @override - C foldLeft(C b, C Function(C b, T2 a) f) => - foldMap>(dualEndoMonoid(), (a) => (C b) => f(b, a))(b); - - /// Return value of type `C` by calling `f` with `b` and the first value of the [Tuple2]. - /// - /// Same as `foldRightFirst`. - C foldLeftFirst(C b, C Function(C b, T1 a) f) => - foldMapFirst>(dualEndoMonoid(), (a) => (C b) => f(b, a))(b); - - /// Return value of type `C` by applying `f` on `monoid`. - @override - C foldMap(Monoid monoid, C Function(T2 a) f) => - foldRight(monoid.empty, (c, b) => monoid.combine(c, f(b))); - - /// Return value of type `C` by applying `f` on `monoid`. - C foldMapFirst(Monoid monoid, C Function(T1 a) f) => - foldRightFirst(monoid.empty, (c, t) => monoid.combine(f(t), c)); - - /// Return value of type `C` by calling `f` with `b` and the second value of the [Tuple2]. - @override - C foldRightWithIndex(C c, C Function(int i, C acc, T2 b) f) => - foldRight>( - Tuple2(c, length() - 1), - (t, a) => Tuple2(f(t.second, t.first, a), t.second - 1), - ).first; - - /// Return value of type `C` by calling `f` with `b` and the first value of the [Tuple2]. - C foldRightFirstWithIndex(C c, C Function(int i, C c, T1 b) f) => - foldRightFirst>( - Tuple2(c, length() - 1), - (t, a) => Tuple2(f(t.second, t.first, a), t.second - 1), - ).first; - - /// Return value of type `C` by calling `f` with `b` and the second value of the [Tuple2]. - @override - C foldLeftWithIndex(C c, C Function(int i, C acc, T2 b) f) => - foldLeft>( - Tuple2(c, 0), - (t, a) => Tuple2(f(t.second, t.first, a), t.second + 1), - ).first; - - /// Return value of type `C` by calling `f` with `b` and the first value of the [Tuple2]. - C foldLeftFirstWithIndex(C c, C Function(int i, C c, T1 b) f) => - foldLeftFirst>( - Tuple2(c, 0), - (t, a) => Tuple2(f(t.second, t.first, a), t.second + 1), - ).first; - - /// Returns `1`. - @override - int length() => foldLeft(0, (b, _) => b + 1); - - /// Return the result of calling `predicate` on the second value of the [Tuple2]. - @override - bool any(bool Function(T2 a) predicate) => foldMap(boolOrMonoid(), predicate); - - /// Return the result of calling `predicate` on the second value of the [Tuple2]. - @override - bool all(bool Function(T2 a) predicate) => - foldMap(boolAndMonoid(), predicate); - - /// Combine the second value of [Tuple2] using `monoid`. - /// ```dart - /// const tuple = Tuple2('abc', 10); - /// final ap = tuple.concatenate(Monoid.instance(0, (a1, a2) => a1 + a2)); - /// expect(ap, 10); - /// ``` - @override - T2 concatenate(Monoid monoid) => foldMap(monoid, identity); - - /// Swap types `T1` and `T2` of [Tuple2]. - Tuple2 swap() => Tuple2(_value2, _value1); - - /// Create a copy of this [Tuple] by changing `value1` and/or `value2`. - Tuple2 copyWith({ - T1? value1, - T2? value2, - }) => - Tuple2(value1 ?? _value1, value2 ?? _value2); - - @override - String toString() => 'Tuple2($_value1, $_value2)'; - - @override - bool operator ==(Object other) => - (other is Tuple2) && other._value1 == _value1 && other._value2 == _value2; - - @override - int get hashCode => _value1.hashCode ^ _value2.hashCode; -} diff --git a/packages/fpdart/test/src/tuple_test.dart b/packages/fpdart/test/src/tuple_test.dart deleted file mode 100644 index fa0a1474..00000000 --- a/packages/fpdart/test/src/tuple_test.dart +++ /dev/null @@ -1,209 +0,0 @@ -import 'package:fpdart/src/tuple.dart'; -import 'package:fpdart/src/typeclass/monoid.dart'; -import 'package:test/test.dart'; - -void main() { - group('Tuple', () { - test('first', () { - const tuple = Tuple2('abc', 10); - expect(tuple.first, 'abc'); - }); - - test('second', () { - const tuple = Tuple2('abc', 10); - expect(tuple.second, 10); - }); - - test('mapFirst', () { - const tuple = Tuple2('abc', 10); - final map = tuple.mapFirst((v1) => v1.length); - expect(map.first, 3); - }); - - test('mapSecond', () { - const tuple = Tuple2('abc', 10); - final map = tuple.mapSecond((v2) => '$v2'); - expect(map.second, '10'); - }); - - test('mapBoth', () { - const tuple = Tuple2('abc', 10); - final map = tuple.mapBoth((v1, v2) => Tuple2('$v2', v1.length)); - expect(map.first, '10'); - expect(map.second, 3); - }); - - test('map', () { - const tuple = Tuple2('abc', 10); - final map = tuple.map((v2) => '$v2'); - 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); - expect(ap, 13); - }); - - test('extend', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.extend((t) => t.second * 2 + t.first.length); - expect(ap.first, 'abc'); - expect(ap.second, 23); - }); - - test('extendFirst', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.extendFirst((t) => t.second * 2 + t.first.length); - expect(ap.first, 23); - expect(ap.second, 10); - }); - - test('duplicate', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.duplicate(); - expect(ap.first, 'abc'); - expect(ap.second.first, 'abc'); - expect(ap.second.second, 10); - }); - - test('foldRight', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldRight(10, (acc, a) => acc + a); - expect(ap, 20); - }); - - test('foldLeft', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldLeft(10, (acc, a) => acc + a); - expect(ap, 20); - }); - - test('foldRightFirst', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldRightFirst(10, (acc, a) => acc + a.length); - expect(ap, 13); - }); - - test('foldLeftFirst', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldLeftFirst(10, (acc, a) => acc + a.length); - expect(ap, 13); - }); - - test('foldRightWithIndex', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldRightWithIndex(10, (i, acc, a) => acc + a); - expect(ap, 20); - }); - - test('foldLeftWithIndex', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldLeftWithIndex(10, (i, acc, a) => acc + a); - expect(ap, 20); - }); - - test('foldRightFirstWithIndex', () { - const tuple = Tuple2('abc', 10); - final ap = - tuple.foldRightFirstWithIndex(10, (i, acc, a) => acc + a.length); - expect(ap, 13); - }); - - test('foldLeftFirstWithIndex', () { - const tuple = Tuple2('abc', 10); - final ap = - tuple.foldLeftFirstWithIndex(10, (i, acc, a) => acc + a.length); - expect(ap, 13); - }); - - test('foldMap', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldMap( - Monoid.instance(0, (a1, a2) => a1 + a2), (a) => a + 10); - expect(ap, 20); - }); - - test('foldMapFirst', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.foldMapFirst( - Monoid.instance(0, (a1, a2) => a1 + a2), (a) => a.length + 10); - expect(ap, 13); - }); - - test('length', () { - const tuple1 = Tuple2('abc', 10); - const tuple2 = Tuple2(0.4, 'ab'); - const tuple3 = Tuple2(10, 0.2); - expect(tuple1.length(), 1); - expect(tuple2.length(), 1); - expect(tuple3.length(), 1); - }); - - test('all', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.all((a) => a > 5); - expect(ap, true); - }); - - test('any', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.any((a) => a > 5); - expect(ap, true); - }); - - test('swap', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.swap(); - expect(ap.first, 10); - expect(ap.second, 'abc'); - }); - - test('concatenate', () { - const tuple = Tuple2('abc', 10); - final ap = tuple.concatenate(Monoid.instance(0, (a1, a2) => a1 + a2)); - expect(ap, 10); - }); - - test('toString', () { - const tuple = Tuple2('abc', 10); - expect(tuple.toString(), 'Tuple2(abc, 10)'); - }); - - test('copyWith', () { - const tuple = Tuple2('abc', 10); - final c1 = tuple.copyWith(value1: 'def'); - final c2 = tuple.copyWith(value2: 11); - final cb = tuple.copyWith(value1: '0', value2: 0); - expect(c1.first, 'def'); - expect(c2.second, 11); - expect(cb.first, '0'); - expect(cb.second, 0); - }); - - test('Tuple2 == Tuple2', () { - const t1 = Tuple2('abc', 10); - const t2 = Tuple2('abc', 10); - const t3 = Tuple2('abc', 9); - const t4 = Tuple2(0.4, 'abc'); - const map1 = {'t1': t1, 't2': t1}; - const map2 = {'t1': t1, 't2': t2}; - const map3 = {'t1': t1, 't2': t3}; - const map4 = {'t1': t1, 't2': t4}; - expect(t1, t1); - expect(t1, t2); - expect(t1 == t3, false); - expect(map1, map1); - expect(map1, map2); - expect(map1 == map3, false); - expect(map1 == map4, false); - }); - }); -} From 776a32fc35c9ca45d0f857da808fe5825fc55e84 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 19:36:53 +0200 Subject: [PATCH 24/71] updated api to records --- packages/fpdart/lib/src/either.dart | 22 ++- .../lib/src/extension/list_extension.dart | 127 ++++++++---------- .../lib/src/extension/map_extension.dart | 9 +- packages/fpdart/lib/src/option.dart | 14 +- packages/fpdart/lib/src/state.dart | 23 ++-- packages/fpdart/lib/src/state_async.dart | 25 ++-- .../fpdart/lib/src/typeclass/filterable.dart | 3 +- .../fpdart/lib/src/typeclass/foldable.dart | 33 +++-- packages/fpdart/lib/src/typedef.dart | 3 +- 9 files changed, 117 insertions(+), 142 deletions(-) diff --git a/packages/fpdart/lib/src/either.dart b/packages/fpdart/lib/src/either.dart index e8f4c30d..0d08961e 100644 --- a/packages/fpdart/lib/src/either.dart +++ b/packages/fpdart/lib/src/either.dart @@ -2,7 +2,6 @@ import 'function.dart'; import 'io_either.dart'; import 'option.dart'; import 'task_either.dart'; -import 'tuple.dart'; import 'typeclass/typeclass.export.dart'; import 'typedef.dart'; @@ -78,19 +77,19 @@ sealed class Either extends HKT2<_EitherHKT, L, R> /// If this [Either] is [Left], return `b`. @override C foldRightWithIndex(C c, C Function(int i, C acc, R b) f) => - foldRight>( - Tuple2(c, length() - 1), - (t, b) => Tuple2(f(t.second, t.first, b), t.second - 1), - ).first; + foldRight<(C, int)>( + (c, length() - 1), + (t, b) => (f(t.$2, t.$1, b), t.$2 - 1), + ).$1; /// Return the result of `f` called with `b` and the value of [Right]. /// If this [Either] is [Left], return `b`. @override C foldLeftWithIndex(C c, C Function(int i, C acc, R b) f) => - foldLeft>( - Tuple2(c, 0), - (t, b) => Tuple2(f(t.second, t.first, b), t.second + 1), - ).first; + foldLeft<(C, int)>( + (c, 0), + (t, b) => (f(t.$2, t.$1, b), t.$2 + 1), + ).$1; /// Returns `1` when [Either] is [Right], `0` otherwise. @override @@ -369,8 +368,7 @@ sealed class Either extends HKT2<_EitherHKT, L, R> /// Extract all the [Left] and [Right] values from a `List>` and /// return them in two partitioned [List] inside [Tuple2]. /// {@endtemplate} - static Tuple2, List> partitionEithers( - List> list) { + static (List, List) partitionEithers(List> list) { final resultListLefts = []; final resultListRights = []; for (var i = 0; i < list.length; i++) { @@ -386,7 +384,7 @@ sealed class Either extends HKT2<_EitherHKT, L, R> } } - return Tuple2(resultListLefts, resultListRights); + return (resultListLefts, resultListRights); } /// Flat a [Either] contained inside another [Either] to be a single [Either]. diff --git a/packages/fpdart/lib/src/extension/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart index 265c0af9..7876bec0 100644 --- a/packages/fpdart/lib/src/extension/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -7,7 +7,6 @@ import '../option.dart'; import '../task.dart'; import '../task_either.dart'; import '../task_option.dart'; -import '../tuple.dart'; import '../typeclass/order.dart'; /// Functional programming functions on a mutable dart [Iterable] using `fpdart`. @@ -31,8 +30,8 @@ extension FpdartOnMutableIterable on Iterable { /// final zipList = list1.zip(list2); /// print(zipList); // -> [Tuple2(a, 1), Tuple2(b, 2)] /// ``` - Iterable> zip(Iterable lb) => - zipWith>((a) => (b) => Tuple2(a, b))(lb); + Iterable<(T, B)> zip(Iterable lb) => + zipWith((a) => (b) => (a, b))(lb); /// Get the first element of the list. /// If the list is empty, return [None]. @@ -73,107 +72,97 @@ extension FpdartOnMutableIterable on Iterable { /// Extract all elements **starting from the first** as long as `predicate` returns `true`. Iterable takeWhileLeft(bool Function(T t) predicate) => - foldLeft>>( - const Tuple2(true, []), + foldLeft<(bool, Iterable)>( + (true, []), (a, e) { - if (!a.first) { + if (!a.$1) { return a; } final check = predicate(e); - return check - ? Tuple2(check, [...a.second, e]) - : Tuple2(check, a.second); + return check ? (check, [...a.$2, e]) : (check, a.$2); }, - ).second; + ).$2; /// Remove all elements **starting from the first** as long as `predicate` returns `true`. Iterable dropWhileLeft(bool Function(T t) predicate) => - foldLeft>>( - const Tuple2(true, []), + foldLeft<(bool, Iterable)>( + (true, []), (a, e) { - if (!a.first) { - return Tuple2(a.first, a.second.append(e)); + if (!a.$1) { + return (a.$1, a.$2.append(e)); } final check = predicate(e); - return check - ? Tuple2(check, a.second) - : Tuple2(check, a.second.append(e)); + return check ? (check, a.$2) : (check, a.$2.append(e)); }, - ).second; + ).$2; /// Extract all elements **starting from the last** as long as `predicate` returns `true`. Iterable takeWhileRight(bool Function(T t) predicate) => - foldRight>>( - const Tuple2(true, []), + foldRight<(bool, Iterable)>( + const (true, []), (e, a) { - if (!a.first) { + if (!a.$1) { return a; } final check = predicate(e); - return check - ? Tuple2(check, a.second.prepend(e)) - : Tuple2(check, a.second); + return check ? (check, a.$2.prepend(e)) : (check, a.$2); }, - ).second; + ).$2; /// Remove all elements **starting from the last** as long as `predicate` returns `true`. Iterable dropWhileRight(bool Function(T t) predicate) => - foldRight>>( - const Tuple2(true, []), + foldRight<(bool, Iterable)>( + const (true, []), (e, a) { - if (!a.first) { - return Tuple2(a.first, a.second.prepend(e)); + if (!a.$1) { + return (a.$1, a.$2.prepend(e)); } final check = predicate(e); - return check - ? Tuple2(check, a.second) - : Tuple2(check, a.second.prepend(e)); + return check ? (check, a.$2) : (check, a.$2.prepend(e)); }, - ).second; + ).$2; /// Return a [Tuple2] where first element is longest prefix (possibly empty) of this [Iterable] /// with elements that **satisfy** `predicate` and second element is the remainder of the [Iterable]. - Tuple2, Iterable> span(bool Function(T t) predicate) => - foldLeft, Iterable>>>( - const Tuple2(true, Tuple2([], [])), + (Iterable, Iterable) span(bool Function(T t) predicate) => + foldLeft<(bool, (Iterable, Iterable))>( + (true, ([], [])), (a, e) { - if (!a.first) { - return Tuple2( - a.first, a.second.mapSecond((second) => second.append(e))); + if (!a.$1) { + return (a.$1, (a.$2.$1, a.$2.$2.append(e))); } final check = predicate(e); return check - ? Tuple2(check, a.second.mapFirst((first) => first.append(e))) - : Tuple2(check, a.second.mapSecond((second) => second.append(e))); + ? (check, (a.$2.$1.append(e), a.$2.$2)) + : (check, (a.$2.$1, a.$2.$2.append(e))); }, - ).second; + ).$2; /// Return a [Tuple2] where first element is longest prefix (possibly empty) of this [Iterable] /// with elements that **do not satisfy** `predicate` and second element is the remainder of the [Iterable]. - Tuple2, Iterable> breakI(bool Function(T t) predicate) => - foldLeft, Iterable>>>( - const Tuple2(false, Tuple2([], [])), + (Iterable, Iterable) breakI(bool Function(T t) predicate) => + foldLeft<(bool, (Iterable, Iterable))>( + (false, ([], [])), (a, e) { - if (a.first) { - return Tuple2( - a.first, a.second.mapSecond((second) => second.append(e))); + if (a.$1) { + return (a.$1, (a.$2.$1, a.$2.$2.append(e))); } final check = predicate(e); return check - ? Tuple2(check, a.second.mapSecond((second) => second.append(e))) - : Tuple2(check, a.second.mapFirst((first) => first.append(e))); + ? (check, (a.$2.$1, a.$2.$2.append(e))) + : (check, (a.$2.$1.append(e), a.$2.$2)); }, - ).second; + ).$2; /// Return a [Tuple2] where first element is an [Iterable] with the first `n` elements of this [Iterable], /// and the second element contains the rest of the [Iterable]. - Tuple2, Iterable> splitAt(int n) => Tuple2(take(n), skip(n)); + (Iterable, Iterable) splitAt(int n) => (take(n), skip(n)); /// Check if `element` is contained inside this [Iterable]. /// @@ -219,15 +208,13 @@ extension FpdartOnMutableIterable on Iterable { /// Remove the **first occurrence** of `element` from this [Iterable]. Iterable delete(T element) => - foldLeft>>(const Tuple2(true, []), (a, e) { - if (!a.first) { - return a.mapSecond((second) => second.append(e)); + foldLeft<(bool, Iterable)>((true, []), (a, e) { + if (!a.$1) { + return (a.$1, a.$2.append(e)); } - return e == element - ? a.mapFirst((first) => false) - : a.mapSecond((second) => second.append(e)); - }).second; + return e == element ? (false, a.$2.append(e)) : (a.$1, a.$2.append(e)); + }).$2; /// The largest element of this [Iterable] based on `order`. /// @@ -274,10 +261,10 @@ extension FpdartOnMutableIterable on Iterable { /// **from the first to the last** using their index. B foldLeftWithIndex( B initialValue, B Function(B accumulator, T element, int index) f) => - fold>( - Tuple2(initialValue, 0), - (p, e) => Tuple2(f(p.first, e, p.second), p.second + 1), - ).first; + fold<(B, int)>( + (initialValue, 0), + (p, e) => (f(p.$1, e, p.$2), p.$2 + 1), + ).$1; /// Fold a [List] into a single value by aggregating each element of the list /// **from the last to the first**. @@ -288,10 +275,10 @@ extension FpdartOnMutableIterable on Iterable { /// **from the last to the first** using their index. B foldRightWithIndex( B initialValue, B Function(T element, B accumulator, int index) f) => - foldRight>( - Tuple2(initialValue, 0), - (e, p) => Tuple2(f(e, p.first, p.second), p.second + 1), - ).first; + foldRight<(B, int)>( + (initialValue, 0), + (e, p) => (f(e, p.$1, p.$2), p.$2 + 1), + ).$1; /// Map [Iterable] from type `T` to type `B` using the index. Iterable mapWithIndex(B Function(T t, int index) f) => @@ -338,8 +325,8 @@ extension FpdartOnMutableIterable on Iterable { /// Return a [Tuple2] where the first element is an [Iterable] with all the elements /// of this [Iterable] that do not satisfy `f` and the second all the elements that /// do satisfy f. - Tuple2, Iterable> partition(bool Function(T t) f) => - Tuple2(filter((t) => !f(t)), filter(f)); + (Iterable, Iterable) partition(bool Function(T t) f) => + (filter((t) => !f(t)), filter(f)); /// Sort [Iterable] based on [DateTime] extracted from type `T` using `getDate`. /// @@ -531,7 +518,7 @@ extension FpdartSequenceIterableEither on Iterable> { List leftsEither() => Either.lefts(toList()); /// {@macro fpdart_partition_eithers_either} - Tuple2, List> partitionEithersEither() => + (List, List) partitionEithersEither() => Either.partitionEithers(toList()); } diff --git a/packages/fpdart/lib/src/extension/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart index f4b3274f..1ac50fbb 100644 --- a/packages/fpdart/lib/src/extension/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -1,5 +1,4 @@ import '../option.dart'; -import '../tuple.dart'; import '../typeclass/eq.dart'; import '../typeclass/order.dart'; import '../typedef.dart'; @@ -81,9 +80,9 @@ extension FpdartOnMutableMap on Map { Option lookup(K key) => Option.fromNullable(this[key]); /// Get the value and key at given `key` if present, otherwise return [None]. - Option> lookupWithKey(K key) { + Option<(K, V)> lookupWithKey(K key) { final value = this[key]; - return value != null ? some(tuple2(key, value)) : none(); + return value != null ? some((key, value)) : none(); } /// Return an [Option] that conditionally accesses map keys, only if they match the @@ -181,8 +180,8 @@ extension FpdartOnMutableMap on Map { }; /// Delete a key and value from a this [Map], returning the deleted value as well as the subsequent [Map]. - Option>> Function(K key) pop(Eq eq) => - (K key) => lookup(key).map((v) => tuple2(v, deleteAt(eq)(key))); + Option<(V, Map)> Function(K key) pop(Eq eq) => + (K key) => lookup(key).map((v) => (v, deleteAt(eq)(key))); /// Apply `fun` to all the values of this [Map] sorted based on `order` on their key, /// and return the result of combining all the intermediate values. diff --git a/packages/fpdart/lib/src/option.dart b/packages/fpdart/lib/src/option.dart index 1798a529..8bc24c29 100644 --- a/packages/fpdart/lib/src/option.dart +++ b/packages/fpdart/lib/src/option.dart @@ -3,7 +3,6 @@ import 'extension/option_extension.dart'; import 'function.dart'; import 'io_option.dart'; import 'task_option.dart'; -import 'tuple.dart'; import 'typeclass/applicative.dart'; import 'typeclass/eq.dart'; import 'typeclass/extend.dart'; @@ -230,14 +229,13 @@ sealed class Option extends HKT<_OptionHKT, T> /// - if `f` applied to its value returns `false`, then the tuple contains this [Option] as first value /// Otherwise the tuple contains both [None]. @override - Tuple2, Option> partition(bool Function(T t) f) => - Tuple2(filter((a) => !f(a)), filter(f)); + (Option, Option) partition(bool Function(T t) f) => + (filter((a) => !f(a)), filter(f)); /// Return a [Tuple2] that contains as first value a [Some] when `f` returns [Left], /// otherwise the [Some] will be the second value of the tuple. @override - Tuple2, Option> partitionMap( - Either Function(T t) f) => + (Option, Option) partitionMap(Either Function(T t) f) => Option.separate(map(f)); /// If this [Option] is a [Some], then return the result of calling `then`. @@ -431,10 +429,10 @@ sealed class Option extends HKT<_OptionHKT, T> /// /// The value on the left of the [Either] will be the first value of the tuple, /// while the right value of the [Either] will be the second of the tuple. - static Tuple2, Option> separate(Option> m) => + static (Option, Option) separate(Option> m) => m.match( - () => Tuple2(const Option.none(), const Option.none()), - (either) => Tuple2(either.getLeft(), either.getRight()), + () => (const Option.none(), const Option.none()), + (either) => (either.getLeft(), either.getRight()), ); /// Build an `Eq evaluate(S state) async => (await run(state)).first; + Future evaluate(S state) async => (await run(state)).$1; /// Execute `run` and extract the state `S`. /// /// To extract both the value and the state use `run`. /// /// To extract only the value `A` use `evaluate`. - Future execute(S state) async => (await run(state)).second; + Future execute(S state) async => (await run(state)).$2; /// Chain a request that returns another [StateAsync], execute it, ignore /// the result, and return the same value as the current [StateAsync]. @@ -125,7 +122,7 @@ final class StateAsync extends HKT2<_StateAsyncHKT, S, A> /// To extract only the value `A` use `evaluate`. /// /// To extract only the state `S` use `execute`. - Future> run(S state) => _run(state); + Future<(A, S)> run(S state) => _run(state); @override bool operator ==(Object other) => (other is StateAsync) && other._run == _run; diff --git a/packages/fpdart/lib/src/typeclass/filterable.dart b/packages/fpdart/lib/src/typeclass/filterable.dart index 9cbc9cf4..e0a4663f 100644 --- a/packages/fpdart/lib/src/typeclass/filterable.dart +++ b/packages/fpdart/lib/src/typeclass/filterable.dart @@ -1,6 +1,5 @@ import '../either.dart'; import '../option.dart'; -import '../tuple.dart'; import '../typedef.dart'; import 'functor.dart'; import 'hkt.dart'; @@ -14,7 +13,7 @@ mixin Filterable on HKT, Functor { /// Partition a data structure based on a boolean predicate. Separated partition(bool Function(A a) f) => - Tuple2(filter((a) => !f(a)), filter(f)); + (filter((a) => !f(a)), filter(f)); /// Partition a data structure based on an [Either] predicate. Separated partitionMap(Either Function(A a) f); diff --git a/packages/fpdart/lib/src/typeclass/foldable.dart b/packages/fpdart/lib/src/typeclass/foldable.dart index 3e28d587..4fb3615f 100644 --- a/packages/fpdart/lib/src/typeclass/foldable.dart +++ b/packages/fpdart/lib/src/typeclass/foldable.dart @@ -1,5 +1,4 @@ import '../function.dart'; -import '../tuple.dart'; import '../typedef.dart'; import 'hkt.dart'; import 'monoid.dart'; @@ -20,16 +19,16 @@ mixin Foldable on HKT { foldRight(monoid.empty, (b, a) => monoid.combine(f(a), b)); B foldRightWithIndex(B b, B Function(int i, B acc, A a) f) => - foldRight>( - Tuple2(b, length() - 1), - (t, a) => Tuple2(f(t.second, t.first, a), t.second - 1), - ).first; + foldRight<(B, int)>( + (b, length() - 1), + (t, a) => (f(t.$2, t.$1, a), t.$2 - 1), + ).$1; B foldLeftWithIndex(B b, B Function(int i, B acc, A a) f) => - foldLeft>( - Tuple2(b, 0), - (t, a) => Tuple2(f(t.second, t.first, a), t.second + 1), - ).first; + foldLeft<(B, int)>( + (b, 0), + (t, a) => (f(t.$2, t.$1, a), t.$2 + 1), + ).$1; int length() => foldLeft(0, (b, _) => b + 1); @@ -57,16 +56,16 @@ mixin Foldable2 on HKT2 { foldRight(monoid.empty, (c, b) => monoid.combine(f(b), c)); C foldRightWithIndex(C c, C Function(int i, C acc, B b) f) => - foldRight>( - Tuple2(c, length() - 1), - (t, b) => Tuple2(f(t.second, t.first, b), t.second - 1), - ).first; + foldRight<(C, int)>( + (c, length() - 1), + (t, b) => (f(t.$2, t.$1, b), t.$2 - 1), + ).$1; C foldLeftWithIndex(C c, C Function(int i, C acc, B b) f) => - foldLeft>( - Tuple2(c, 0), - (t, b) => Tuple2(f(t.second, t.first, b), t.second + 1), - ).first; + foldLeft<(C, int)>( + (c, 0), + (t, b) => (f(t.$2, t.$1, b), t.$2 + 1), + ).$1; int length() => foldLeft(0, (b, _) => b + 1); diff --git a/packages/fpdart/lib/src/typedef.dart b/packages/fpdart/lib/src/typedef.dart index f9e465b6..6c1895ce 100644 --- a/packages/fpdart/lib/src/typedef.dart +++ b/packages/fpdart/lib/src/typedef.dart @@ -1,6 +1,5 @@ -import 'tuple.dart'; import 'typeclass/hkt.dart'; typedef Endo = A Function(A a); -typedef Separated = Tuple2, HKT>; +typedef Separated = (HKT, HKT); typedef Magma = T Function(T x, T y); From ebbaaeb4a4e736891b9cf582cac9c402ce4f845a Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 19:39:22 +0200 Subject: [PATCH 25/71] update docs to records --- examples/read_write_file/main.dart | 8 ++--- examples/read_write_file/pubspec.yaml | 2 +- packages/fpdart/example/src/state/state1.dart | 32 +++++++++---------- .../example/src/state_async/state_async1.dart | 5 +-- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/examples/read_write_file/main.dart b/examples/read_write_file/main.dart index 3b0a5192..3ae487bf 100644 --- a/examples/read_write_file/main.dart +++ b/examples/read_write_file/main.dart @@ -32,21 +32,21 @@ class FoundWord { const searchWords = ['that', 'and', 'for']; Iterable collectFoundWords( - Iterable> iterable, + Iterable<(String, String)> iterable, ) => iterable.flatMapWithIndex( (tuple, index) => searchWords.foldLeftWithIndex>( [], (acc, word, wordIndex) => - tuple.second.toLowerCase().split(' ').contains(word) + tuple.$2.toLowerCase().split(' ').contains(word) ? [ ...acc, FoundWord( index, word, wordIndex, - tuple.second.replaceAll(word, '<\$>'), - tuple.first, + tuple.$2.replaceAll(word, '<\$>'), + tuple.$1, ), ] : acc, diff --git a/examples/read_write_file/pubspec.yaml b/examples/read_write_file/pubspec.yaml index fbb0aae4..80ea3f86 100644 --- a/examples/read_write_file/pubspec.yaml +++ b/examples/read_write_file/pubspec.yaml @@ -7,7 +7,7 @@ description: Example of Functional programming in Dart and Flutter using fpdart. author: Maglione Sandro environment: - sdk: ">=2.13.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: fpdart: diff --git a/packages/fpdart/example/src/state/state1.dart b/packages/fpdart/example/src/state/state1.dart index f14358c0..a007eafa 100644 --- a/packages/fpdart/example/src/state/state1.dart +++ b/packages/fpdart/example/src/state/state1.dart @@ -1,6 +1,5 @@ /// Source: http://www.learnyouahaskell.com/for-a-few-monads-more import 'package:fpdart/src/state.dart'; -import 'package:fpdart/src/tuple.dart'; import 'package:fpdart/src/unit.dart'; /// [Stack] is an alias for [List]. @@ -12,43 +11,44 @@ const Stack stack = ['a', 'b', 'c']; /// /// We need to explicitly pass the state [Stack] every time we call `pop` or `push`. -Tuple2 pop(Stack s) => - Tuple2(s.last, s.sublist(0, s.length - 1)); +(String, Stack) pop(Stack s) => (s.last, s.sublist(0, s.length - 1)); -Tuple2 push(String value, Stack s) => Tuple2(unit, [...s, value]); +(Unit, Stack) push(String value, Stack s) => (unit, [...s, value]); /// Example Using State Monad /// /// The global variable [Stack] is hidden using [State]. -State popState() => - State((s) => Tuple2(s.last, s.sublist(0, s.length - 1))); +State popState() => State( + (s) => (s.last, s.sublist(0, s.length - 1)), + ); -State pushState(String value) => - State((s) => Tuple2(unit, [...s, value])); +State pushState(String value) => State( + (s) => (unit, [...s, value]), + ); void main() { // Without State Monad final pop1NoState = pop(stack); - final push1NoState = push('d', pop1NoState.second); - final pop2NoState = pop(push1NoState.second); + final push1NoState = push('d', pop1NoState.$2); + final pop2NoState = pop(push1NoState.$2); print('No State'); print(stack); print('Pop'); - print(pop1NoState.second); - print(pop1NoState.first); + print(pop1NoState.$2); + print(pop1NoState.$1); print("Push 'd'"); - print(push1NoState.second); + print(push1NoState.$2); print('Pop'); - print(pop2NoState.second); - print(pop2NoState.first); + print(pop2NoState.$2); + print(pop2NoState.$1); // Using State Monad print('---'); print('Using State'); final withState = popState().execute( pushState('d').execute( - popState().run(stack).second, + popState().run(stack).$2, ), ); final withState2 = popState()(pushState('d'))(popState()).run(stack); diff --git a/packages/fpdart/example/src/state_async/state_async1.dart b/packages/fpdart/example/src/state_async/state_async1.dart index 00219bf2..a44043c6 100644 --- a/packages/fpdart/example/src/state_async/state_async1.dart +++ b/packages/fpdart/example/src/state_async/state_async1.dart @@ -4,10 +4,7 @@ Future add10(int previous) async => previous + 10; Future main() async { final stateAsync = StateAsync( - (state) async => tuple2( - unit, - await add10(state), - ), + (state) async => (unit, await add10(state)), ); final result = await stateAsync.execute(10); From 497eba17c4228d96db8cc9d75fe3b30aa1c74df2 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 19:47:50 +0200 Subject: [PATCH 26/71] update tests with records --- .../bin/managing_imports.dart | 27 +--- examples/managing_imports/lib/functional.dart | 3 - examples/managing_imports/pubspec.yaml | 3 +- packages/fpdart/test/src/either_test.dart | 4 +- .../src/extension/list_extension_test.dart | 22 +-- .../src/extension/map_extension_test.dart | 8 +- packages/fpdart/test/src/option_test.dart | 32 ++-- .../fpdart/test/src/state_async_test.dart | 137 ++++++++---------- packages/fpdart/test/src/state_test.dart | 120 +++++++-------- 9 files changed, 162 insertions(+), 194 deletions(-) diff --git a/examples/managing_imports/bin/managing_imports.dart b/examples/managing_imports/bin/managing_imports.dart index 86da41fb..8405dd95 100644 --- a/examples/managing_imports/bin/managing_imports.dart +++ b/examples/managing_imports/bin/managing_imports.dart @@ -1,32 +1,17 @@ import 'package:managing_imports/functional.dart'; import 'package:test/test.dart'; -void main(List arguments) { - // borrow the flatMap test from state_test.dart +void main() { + /// Borrow the `flatMap` test from `state_test.dart` test('flatMap', () { - final state = FpState, int>((s) => Tuple2(s.first, s.sublist(1))); + final state = FpState, int>((s) => (s.first, s.sublist(1))); final ap = state.flatMap( (a) => FpState( - (s) => Tuple2(a / 2, s.sublist(1)), + (s) => (a / 2, s.sublist(1)), ), ); final result = ap.run([1, 2, 3, 4, 5]); - expect(result.first, 0.5); - expect(result.second, [3, 4, 5]); - }); - - test('Tuple2', () { - const tuple = Tuple2(1, 2); - // this is the item access syntax for the fpdart package - expect(tuple.first, 1); - expect(tuple.second, 2); - }); - - test('Tuple3', () { - const tuple = Tuple3(1, 2, 3); - // this is the item access syntax for the tuple package - expect(tuple.item1, 1); - expect(tuple.item2, 2); - expect(tuple.item3, 3); + expect(result.$1, 0.5); + expect(result.$2, [3, 4, 5]); }); } diff --git a/examples/managing_imports/lib/functional.dart b/examples/managing_imports/lib/functional.dart index 59a6ec5b..c5ba2864 100644 --- a/examples/managing_imports/lib/functional.dart +++ b/examples/managing_imports/lib/functional.dart @@ -8,9 +8,6 @@ import 'package:fpdart/fpdart.dart' as fpdart show State; // The `fpdart` library is used to create functional programming constructs. export 'package:fpdart/fpdart.dart' hide State; -// The `tuple` library is used to create tuples, which are immutable lists of -// fixed length. -export 'package:tuple/tuple.dart' show Tuple3, Tuple4, Tuple5, Tuple6, Tuple7; /// A type alias for the `State` class from the `fpdart` library. typedef FpState = fpdart.State; diff --git a/examples/managing_imports/pubspec.yaml b/examples/managing_imports/pubspec.yaml index 23652282..ebbca2d5 100644 --- a/examples/managing_imports/pubspec.yaml +++ b/examples/managing_imports/pubspec.yaml @@ -4,13 +4,12 @@ version: 1.0.0 publish_to: none environment: - sdk: ">=2.18.6 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: fpdart: path: ../../packages/fpdart test: - tuple: very_good_analysis: dev_dependencies: diff --git a/packages/fpdart/test/src/either_test.dart b/packages/fpdart/test/src/either_test.dart index 2dedde21..81dce2db 100644 --- a/packages/fpdart/test/src/either_test.dart +++ b/packages/fpdart/test/src/either_test.dart @@ -1098,8 +1098,8 @@ void main() { right(3), ]; final result = Either.partitionEithers(list); - expect(result.first, ['a', 'b']); - expect(result.second, [1, 2, 3]); + expect(result.$1, ['a', 'b']); + expect(result.$2, [1, 2, 3]); }); group('safeCast', () { diff --git a/packages/fpdart/test/src/extension/list_extension_test.dart b/packages/fpdart/test/src/extension/list_extension_test.dart index 82a389b5..ea526ba6 100644 --- a/packages/fpdart/test/src/extension/list_extension_test.dart +++ b/packages/fpdart/test/src/extension/list_extension_test.dart @@ -32,7 +32,7 @@ void main() { final list2 = ['a', 'b']; final ap = list1.zip(list2); - expect(eq(ap, [const Tuple2(1, 'a'), const Tuple2(2, 'b')]), true); + expect(eq(ap, [(1, 'a'), (2, 'b')]), true); }); test('filter', () { @@ -217,22 +217,22 @@ void main() { test('span', () { final list1 = [1, 2, 3, 4]; final ap = list1.span((t) => t < 3); - expect(eq(ap.first, [1, 2]), true); - expect(eq(ap.second, [3, 4]), true); + expect(eq(ap.$1, [1, 2]), true); + expect(eq(ap.$2, [3, 4]), true); }); test('breakI', () { final list1 = [1, 2, 3, 4]; final ap = list1.breakI((t) => t > 2); - expect(eq(ap.first, [1, 2]), true); - expect(eq(ap.second, [3, 4]), true); + expect(eq(ap.$1, [1, 2]), true); + expect(eq(ap.$2, [3, 4]), true); }); test('splitAt', () { final list1 = [1, 2, 3, 4]; final ap = list1.splitAt(2); - expect(eq(ap.first, [1, 2]), true); - expect(eq(ap.second, [3, 4]), true); + expect(eq(ap.$1, [1, 2]), true); + expect(eq(ap.$2, [3, 4]), true); }); test('delete', () { @@ -334,8 +334,8 @@ void main() { test('partition', () { final list1 = [2, 4, 5, 6, 1, 3]; final ap = list1.partition((t) => t > 2); - expect(eq(ap.first, [2, 1]), true); - expect(eq(ap.second, [4, 5, 6, 3]), true); + expect(eq(ap.$1, [2, 1]), true); + expect(eq(ap.$2, [4, 5, 6, 3]), true); }); group('all', () { @@ -1236,8 +1236,8 @@ void main() { right(3), ]; final result = list.partitionEithersEither(); - expect(result.first, ['a', 'b']); - expect(result.second, [1, 2, 3]); + expect(result.$1, ['a', 'b']); + expect(result.$2, [1, 2, 3]); }); }); diff --git a/packages/fpdart/test/src/extension/map_extension_test.dart b/packages/fpdart/test/src/extension/map_extension_test.dart index 89b1d890..f678a9a3 100644 --- a/packages/fpdart/test/src/extension/map_extension_test.dart +++ b/packages/fpdart/test/src/extension/map_extension_test.dart @@ -62,8 +62,8 @@ void main() { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final ap = map.lookupWithKey('b'); ap.matchTestSome((t) { - expect(t.first, 'b'); - expect(t.second, 2); + expect(t.$1, 'b'); + expect(t.$2, 2); }); }); @@ -268,8 +268,8 @@ void main() { final ap = map.pop(Eq.instance((a1, a2) => a1 == a2))('b'); expect(map.lookup('b'), isA()); ap.matchTestSome((t) { - expect(t.first, 2); - expect(t.second.lookup('b'), isA()); + expect(t.$1, 2); + expect(t.$2.lookup('b'), isA()); }); }); diff --git a/packages/fpdart/test/src/option_test.dart b/packages/fpdart/test/src/option_test.dart index a28dd43d..543d0572 100644 --- a/packages/fpdart/test/src/option_test.dart +++ b/packages/fpdart/test/src/option_test.dart @@ -230,22 +230,22 @@ void main() { test('Some (true)', () { final option = Option.of(10); final value = option.partition((a) => a > 5); - expect(value.first, isA()); - value.second.matchTestSome((some) => expect(some, 10)); + expect(value.$1, isA()); + value.$2.matchTestSome((some) => expect(some, 10)); }); test('Some (false)', () { final option = Option.of(10); final value = option.partition((a) => a < 5); - value.first.matchTestSome((some) => expect(some, 10)); - expect(value.second, isA()); + value.$1.matchTestSome((some) => expect(some, 10)); + expect(value.$2, isA()); }); test('None', () { final option = Option.none(); final value = option.partition((a) => a > 5); - expect(value.first, isA()); - expect(value.second, isA()); + expect(value.$1, isA()); + expect(value.$2, isA()); }); }); @@ -254,24 +254,24 @@ void main() { final option = Option.of(10); final value = option.partitionMap((a) => Either.of(a / 2)); - expect(value.first, isA()); - value.second.matchTestSome((some) => expect(some, 5.0)); + expect(value.$1, isA()); + value.$2.matchTestSome((some) => expect(some, 5.0)); }); test('Some (left)', () { final option = Option.of(10); final value = option.partitionMap((a) => Either.left('$a')); - value.first.matchTestSome((some) => expect(some, '10')); - expect(value.second, isA()); + value.$1.matchTestSome((some) => expect(some, '10')); + expect(value.$2, isA()); }); test('None', () { final option = Option.none(); final value = option.partitionMap((a) => Either.of(a / 2)); - expect(value.first, isA()); - expect(value.second, isA()); + expect(value.$1, isA()); + expect(value.$2, isA()); }); }); @@ -353,15 +353,15 @@ void main() { group('separate', () { test('Right', () { final option = Option.separate(Option.of(Either.of(10))); - expect(option.first, isA()); - option.second.matchTestSome((some) => expect(some, 10)); + expect(option.$1, isA()); + option.$2.matchTestSome((some) => expect(some, 10)); }); test('Left', () { final option = Option.separate(Option.of(Either.left('none'))); - option.first.matchTestSome((some) => expect(some, 'none')); - expect(option.second, isA()); + option.$1.matchTestSome((some) => expect(some, 'none')); + expect(option.$2, isA()); }); }); diff --git a/packages/fpdart/test/src/state_async_test.dart b/packages/fpdart/test/src/state_async_test.dart index a9080c31..da0fb9cf 100644 --- a/packages/fpdart/test/src/state_async_test.dart +++ b/packages/fpdart/test/src/state_async_test.dart @@ -4,8 +4,7 @@ import 'package:test/test.dart'; void main() { group('StateAsync', () { group('is a', () { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); test('Monad', () { expect(state, isA()); @@ -21,35 +20,33 @@ void main() { }); test('map', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final ap = state.map((a) => a + 1); final result = await ap.run('aaa'); - expect(result.first, 4); - expect(result.second, 'aaaa'); + expect(result.$1, 4); + expect(result.$2, 'aaaa'); }); test('map2', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final state1 = StateAsync( - (s) async => Tuple2(s.length / 2, '${s}b'), + (s) async => (s.length / 2, '${s}b'), ); final ap = state.map2(state1, (a, c) => c * a); final result = await ap.run('aaa'); - expect(result.first, 6); - expect(result.second, 'aaaab'); + expect(result.$1, 6); + expect(result.$2, 'aaaab'); }); test('map3', () async { final state = StateAsync( - (s) async => Tuple2(s.length, '${s}a'), + (s) async => (s.length, '${s}a'), ); final state1 = StateAsync( - (s) async => Tuple2(s.length / 2, '${s}b'), + (s) async => (s.length / 2, '${s}b'), ); final state2 = StateAsync( - (s) async => Tuple2('${s}aaa', '${s}b'), + (s) async => ('${s}aaa', '${s}b'), ); final ap = state.map3( state1, @@ -57,145 +54,137 @@ void main() { (a, c, d) => d.length + (c * a), ); final result = await ap.run('aaa'); - expect(result.first, 14); - expect(result.second, 'aaaabb'); + expect(result.$1, 14); + expect(result.$2, 'aaaabb'); }); test('ap', () async { final state = StateAsync( - (s) async => Tuple2(s.length, '${s}a'), + (s) async => (s.length, '${s}a'), ); final ap = state.ap( StateAsync( - (s) async => Tuple2((int n) => '$n$s', s), + (s) async => ((int n) => '$n$s', s), ), ); final result = await ap.run('aaa'); - expect(result.first, '3aaa'); - expect(result.second, 'aaaa'); + expect(result.$1, '3aaa'); + expect(result.$2, 'aaaa'); }); test('andThen', () async { final state = StateAsync( - (s) async => Tuple2(s.length, '${s}a'), + (s) async => (s.length, '${s}a'), ); final ap = state.andThen( () => StateAsync( - (s) async => Tuple2(s.length / 2, '${s}a'), + (s) async => (s.length / 2, '${s}a'), ), ); final result = await ap.run('aaa'); - expect(result.first, 2); - expect(result.second, 'aaaaa'); + expect(result.$1, 2); + expect(result.$2, 'aaaaa'); }); test('call', () async { final state = StateAsync( - (s) async => Tuple2(s.length, '${s}a'), + (s) async => (s.length, '${s}a'), ); final ap = state( StateAsync( - (s) async => Tuple2(s.length / 2, '${s}a'), + (s) async => (s.length / 2, '${s}a'), ), ); final result = await ap.run('aaa'); - expect(result.first, 2); - expect(result.second, 'aaaaa'); + expect(result.$1, 2); + expect(result.$2, 'aaaaa'); }); test('fromState', () async { - final state = StateAsync.fromState( - State((s) => Tuple2(s.length, '${s}a'))); + final state = + StateAsync.fromState(State((s) => (s.length, '${s}a'))); final result = await state.run('aaa'); - expect(result.first, 3); - expect(result.second, 'aaaa'); + expect(result.$1, 3); + expect(result.$2, 'aaaa'); }); test('pure', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final ap = state.pure(10); final result = await ap.run('aaa'); - expect(result.first, 10); - expect(result.second, 'aaa'); + expect(result.$1, 10); + expect(result.$2, 'aaa'); }); test('flatMap', () async { - final state = StateAsync, int>( - (s) async => Tuple2(s.first, s.sublist(1))); + final state = + StateAsync, int>((s) async => (s.first, s.sublist(1))); final ap = state.flatMap( (a) => StateAsync( - (s) async => Tuple2(a / 2, s.sublist(1)), + (s) async => (a / 2, s.sublist(1)), ), ); final result = await ap.run([1, 2, 3, 4, 5]); - expect(result.first, 0.5); - expect(result.second, [3, 4, 5]); + expect(result.$1, 0.5); + expect(result.$2, [3, 4, 5]); }); test('get', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final ap = state.get(); final result = await ap.run('aaa'); - expect(result.first, 'aaa'); - expect(result.second, 'aaa'); + expect(result.$1, 'aaa'); + expect(result.$2, 'aaa'); }); test('gets', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final ap = state.gets((s) => s.length * 2); final result = await ap.run('aaa'); - expect(result.first, 6); - expect(result.second, 'aaa'); + expect(result.$1, 6); + expect(result.$2, 'aaa'); }); test('modify', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final ap = state.modify((state) => 'b$state'); final result = await ap.run('aaa'); - expect(result.first, unit); - expect(result.second, 'baaa'); + expect(result.$1, unit); + expect(result.$2, 'baaa'); }); test('put', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final ap = state.put('b'); final result = await ap.run('aaa'); - expect(result.second, 'b'); + expect(result.$2, 'b'); }); test('evaluate', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final result = await state.evaluate('aaa'); expect(result, 3); }); test('execute', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final result = await state.execute('aaa'); expect(result, 'aaaa'); }); test('run', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); final result = await state.run('aaa'); - expect(result, isA()); - expect(result.first, 3); - expect(result.second, 'aaaa'); + expect(result, isA()); + expect(result.$1, 3); + expect(result.$2, 'aaaa'); }); test('flatten', () async { final state = StateAsync>( - (s) async => Tuple2( + (s) async => ( StateAsync( - (s) async => Tuple2(s.length, '${s}a'), + (s) async => (s.length, '${s}a'), ), '${s}a', ), @@ -203,25 +192,23 @@ void main() { final ap = StateAsync.flatten(state); expect(ap, isA>()); final result = await ap.run('aaa'); - expect(result.first, 4); - expect(result.second, 'aaaaa'); + expect(result.$1, 4); + expect(result.$2, 'aaaaa'); }); }); test('chainFirst', () async { - final state = - StateAsync((s) async => Tuple2(s.length, '${s}a')); + final state = StateAsync((s) async => (s.length, '${s}a')); var sideEffect = 10; final chain = state.chainFirst((b) { sideEffect = 100; - return StateAsync( - (s) async => Tuple2(s.length / 2, 'z${s}')); + return StateAsync((s) async => (s.length / 2, 'z${s}')); }); final result = await chain.run('abc'); - expect(result.first, 3); + expect(result.$1, 3); // It changes the value of `second`! - expect(result.second, 'zabca'); + expect(result.$2, 'zabca'); expect(sideEffect, 100); }); } diff --git a/packages/fpdart/test/src/state_test.dart b/packages/fpdart/test/src/state_test.dart index cd36fb64..05ed3eee 100644 --- a/packages/fpdart/test/src/state_test.dart +++ b/packages/fpdart/test/src/state_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('State', () { group('is a', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); test('Monad', () { expect(state, isA()); @@ -20,33 +20,33 @@ void main() { }); test('map', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final ap = state.map((a) => a + 1); final result = ap.run('aaa'); - expect(result.first, 4); - expect(result.second, 'aaaa'); + expect(result.$1, 4); + expect(result.$2, 'aaaa'); }); test('map2', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final state1 = State( - (s) => Tuple2(s.length / 2, '${s}b'), + (s) => (s.length / 2, '${s}b'), ); final ap = state.map2(state1, (a, c) => c * a); final result = ap.run('aaa'); - expect(result.first, 6); - expect(result.second, 'aaaab'); + expect(result.$1, 6); + expect(result.$2, 'aaaab'); }); test('map3', () { final state = State( - (s) => Tuple2(s.length, '${s}a'), + (s) => (s.length, '${s}a'), ); final state1 = State( - (s) => Tuple2(s.length / 2, '${s}b'), + (s) => (s.length / 2, '${s}b'), ); final state2 = State( - (s) => Tuple2('${s}aaa', '${s}b'), + (s) => ('${s}aaa', '${s}b'), ); final ap = state.map3( state1, @@ -54,136 +54,136 @@ void main() { (a, c, d) => d.length + (c * a), ); final result = ap.run('aaa'); - expect(result.first, 14); - expect(result.second, 'aaaabb'); + expect(result.$1, 14); + expect(result.$2, 'aaaabb'); }); test('ap', () { final state = State( - (s) => Tuple2(s.length, '${s}a'), + (s) => (s.length, '${s}a'), ); final ap = state.ap( State( - (s) => Tuple2((int n) => '$n$s', s), + (s) => ((int n) => '$n$s', s), ), ); final result = ap.run('aaa'); - expect(result.first, '3aaa'); - expect(result.second, 'aaaa'); + expect(result.$1, '3aaa'); + expect(result.$2, 'aaaa'); }); test('andThen', () { final state = State( - (s) => Tuple2(s.length, '${s}a'), + (s) => (s.length, '${s}a'), ); final ap = state.andThen( () => State( - (s) => Tuple2(s.length / 2, '${s}a'), + (s) => (s.length / 2, '${s}a'), ), ); final result = ap.run('aaa'); - expect(result.first, 2); - expect(result.second, 'aaaaa'); + expect(result.$1, 2); + expect(result.$2, 'aaaaa'); }); test('call', () { final state = State( - (s) => Tuple2(s.length, '${s}a'), + (s) => (s.length, '${s}a'), ); final ap = state( State( - (s) => Tuple2(s.length / 2, '${s}a'), + (s) => (s.length / 2, '${s}a'), ), ); final result = ap.run('aaa'); - expect(result.first, 2); - expect(result.second, 'aaaaa'); + expect(result.$1, 2); + expect(result.$2, 'aaaaa'); }); test('toStateAsync', () async { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final ap = state.toStateAsync(); final result = await ap.run('aaa'); - expect(result.first, 3); - expect(result.second, 'aaaa'); + expect(result.$1, 3); + expect(result.$2, 'aaaa'); }); test('pure', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final ap = state.pure(10); final result = ap.run('aaa'); - expect(result.first, 10); - expect(result.second, 'aaa'); + expect(result.$1, 10); + expect(result.$2, 'aaa'); }); test('flatMap', () { - final state = State, int>((s) => Tuple2(s.first, s.sublist(1))); + final state = State, int>((s) => (s.first, s.sublist(1))); final ap = state.flatMap( (a) => State( - (s) => Tuple2(a / 2, s.sublist(1)), + (s) => (a / 2, s.sublist(1)), ), ); final result = ap.run([1, 2, 3, 4, 5]); - expect(result.first, 0.5); - expect(result.second, [3, 4, 5]); + expect(result.$1, 0.5); + expect(result.$2, [3, 4, 5]); }); test('get', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final ap = state.get(); final result = ap.run('aaa'); - expect(result.first, 'aaa'); - expect(result.second, 'aaa'); + expect(result.$1, 'aaa'); + expect(result.$2, 'aaa'); }); test('gets', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final ap = state.gets((s) => s.length * 2); final result = ap.run('aaa'); - expect(result.first, 6); - expect(result.second, 'aaa'); + expect(result.$1, 6); + expect(result.$2, 'aaa'); }); test('modify', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final ap = state.modify((state) => 'b$state'); final result = ap.run('aaa'); - expect(result.first, unit); - expect(result.second, 'baaa'); + expect(result.$1, unit); + expect(result.$2, 'baaa'); }); test('put', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final ap = state.put('b'); final result = ap.run('aaa'); - expect(result.second, 'b'); + expect(result.$2, 'b'); }); test('evaluate', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final result = state.evaluate('aaa'); expect(result, 3); }); test('execute', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final result = state.execute('aaa'); expect(result, 'aaaa'); }); test('run', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); final result = state.run('aaa'); - expect(result, isA()); - expect(result.first, 3); - expect(result.second, 'aaaa'); + expect(result, isA()); + expect(result.$1, 3); + expect(result.$2, 'aaaa'); }); test('flatten', () { final state = State>( - (s) => Tuple2( + (s) => ( State( - (s) => Tuple2(s.length, '${s}a'), + (s) => (s.length, '${s}a'), ), '${s}a', ), @@ -191,23 +191,23 @@ void main() { final ap = State.flatten(state); expect(ap, isA>()); final result = ap.run('aaa'); - expect(result.first, 4); - expect(result.second, 'aaaaa'); + expect(result.$1, 4); + expect(result.$2, 'aaaaa'); }); }); test('chainFirst', () { - final state = State((s) => Tuple2(s.length, '${s}a')); + final state = State((s) => (s.length, '${s}a')); var sideEffect = 10; final chain = state.chainFirst((b) { sideEffect = 100; - return State((s) => Tuple2(s.length / 2, 'z${s}')); + return State((s) => (s.length / 2, 'z${s}')); }); final result = chain.run('abc'); - expect(result.first, 3); + expect(result.$1, 3); // It changes the value of `second`! - expect(result.second, 'zabca'); + expect(result.$2, 'zabca'); expect(sideEffect, 100); }); } From 35ffe9257b291932c82bbd64cc7faa852037d3c9 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 19:53:08 +0200 Subject: [PATCH 27/71] fix broken test --- packages/fpdart/lib/src/extension/list_extension.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fpdart/lib/src/extension/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart index 7876bec0..bbfb5602 100644 --- a/packages/fpdart/lib/src/extension/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -213,7 +213,7 @@ extension FpdartOnMutableIterable on Iterable { return (a.$1, a.$2.append(e)); } - return e == element ? (false, a.$2.append(e)) : (a.$1, a.$2.append(e)); + return e == element ? (false, a.$2) : (a.$1, a.$2.append(e)); }).$2; /// The largest element of this [Iterable] based on `order`. From 12f51b1b7d9f97a7d9a380a2d77eaf94ac717a37 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 20:04:11 +0200 Subject: [PATCH 28/71] removed references to Tuple2 --- examples/read_write_file/main.dart | 2 +- packages/fpdart/example/src/list/zip.dart | 2 +- packages/fpdart/lib/src/either.dart | 2 +- .../fpdart/lib/src/extension/list_extension.dart | 12 ++++++------ packages/fpdart/lib/src/option.dart | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/read_write_file/main.dart b/examples/read_write_file/main.dart index 3ae487bf..8f511adb 100644 --- a/examples/read_write_file/main.dart +++ b/examples/read_write_file/main.dart @@ -6,7 +6,7 @@ import 'package:fpdart/fpdart.dart'; * Read lines from a `.txt` file using [TaskEither] of fpdart. * * This application reads from two files containing english and italian sentences. - * It then uses `zip` to join the two resulting lists together in a `List>`. + * It then uses `zip` to join the two resulting lists together in a `List<(String, String)>`. * * Finally, it uses `flatMap` and `foldLeft` to iterate over the sentences and search words from a predefined list (`searchWords`). * At the end, we have a list of [FoundWord] containing all the sentences and words matched. diff --git a/packages/fpdart/example/src/list/zip.dart b/packages/fpdart/example/src/list/zip.dart index 096be53a..71031c77 100644 --- a/packages/fpdart/example/src/list/zip.dart +++ b/packages/fpdart/example/src/list/zip.dart @@ -4,5 +4,5 @@ void main() { final list1 = ['a', 'b']; final list2 = [1, 2]; final zipList = list1.zip(list2); - print(zipList); // -> [Tuple2(a, 1), Tuple2(b, 2)] + print(zipList); // -> [(a, 1), (b, 2)] } diff --git a/packages/fpdart/lib/src/either.dart b/packages/fpdart/lib/src/either.dart index 0d08961e..c3140e20 100644 --- a/packages/fpdart/lib/src/either.dart +++ b/packages/fpdart/lib/src/either.dart @@ -366,7 +366,7 @@ sealed class Either extends HKT2<_EitherHKT, L, R> /// {@template fpdart_partition_eithers_either} /// Extract all the [Left] and [Right] values from a `List>` and - /// return them in two partitioned [List] inside [Tuple2]. + /// return them in two partitioned [List] inside a record. /// {@endtemplate} static (List, List) partitionEithers(List> list) { final resultListLefts = []; diff --git a/packages/fpdart/lib/src/extension/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart index bbfb5602..eb5b1d06 100644 --- a/packages/fpdart/lib/src/extension/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -23,12 +23,12 @@ extension FpdartOnMutableIterable on Iterable { ]; /// `zip` is used to join elements at the same index from two different [List] - /// into one [List] of [Tuple2]. + /// into one [List] of a record. /// ```dart /// final list1 = ['a', 'b']; /// final list2 = [1, 2]; /// final zipList = list1.zip(list2); - /// print(zipList); // -> [Tuple2(a, 1), Tuple2(b, 2)] + /// print(zipList); // -> [(a, 1), (b, 2)] /// ``` Iterable<(T, B)> zip(Iterable lb) => zipWith((a) => (b) => (a, b))(lb); @@ -126,7 +126,7 @@ extension FpdartOnMutableIterable on Iterable { }, ).$2; - /// Return a [Tuple2] where first element is longest prefix (possibly empty) of this [Iterable] + /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] /// with elements that **satisfy** `predicate` and second element is the remainder of the [Iterable]. (Iterable, Iterable) span(bool Function(T t) predicate) => foldLeft<(bool, (Iterable, Iterable))>( @@ -143,7 +143,7 @@ extension FpdartOnMutableIterable on Iterable { }, ).$2; - /// Return a [Tuple2] where first element is longest prefix (possibly empty) of this [Iterable] + /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] /// with elements that **do not satisfy** `predicate` and second element is the remainder of the [Iterable]. (Iterable, Iterable) breakI(bool Function(T t) predicate) => foldLeft<(bool, (Iterable, Iterable))>( @@ -160,7 +160,7 @@ extension FpdartOnMutableIterable on Iterable { }, ).$2; - /// Return a [Tuple2] where first element is an [Iterable] with the first `n` elements of this [Iterable], + /// Return a record where first element is an [Iterable] with the first `n` elements of this [Iterable], /// and the second element contains the rest of the [Iterable]. (Iterable, Iterable) splitAt(int n) => (take(n), skip(n)); @@ -322,7 +322,7 @@ extension FpdartOnMutableIterable on Iterable { Iterable bindWithIndex(Iterable Function(T t, int index) f) => concatMapWithIndex(f); - /// Return a [Tuple2] where the first element is an [Iterable] with all the elements + /// Return a record where the first element is an [Iterable] with all the elements /// of this [Iterable] that do not satisfy `f` and the second all the elements that /// do satisfy f. (Iterable, Iterable) partition(bool Function(T t) f) => diff --git a/packages/fpdart/lib/src/option.dart b/packages/fpdart/lib/src/option.dart index 8bc24c29..139e3beb 100644 --- a/packages/fpdart/lib/src/option.dart +++ b/packages/fpdart/lib/src/option.dart @@ -224,7 +224,7 @@ sealed class Option extends HKT<_OptionHKT, T> @override Option filterMap(Option Function(T t) f); - /// Return a [Tuple2]. If this [Option] is a [Some]: + /// Return a record. If this [Option] is a [Some]: /// - if `f` applied to its value returns `true`, then the tuple contains this [Option] as second value /// - if `f` applied to its value returns `false`, then the tuple contains this [Option] as first value /// Otherwise the tuple contains both [None]. @@ -232,7 +232,7 @@ sealed class Option extends HKT<_OptionHKT, T> (Option, Option) partition(bool Function(T t) f) => (filter((a) => !f(a)), filter(f)); - /// Return a [Tuple2] that contains as first value a [Some] when `f` returns [Left], + /// Return a record that contains as first value a [Some] when `f` returns [Left], /// otherwise the [Some] will be the second value of the tuple. @override (Option, Option) partitionMap(Either Function(T t) f) => @@ -425,7 +425,7 @@ sealed class Option extends HKT<_OptionHKT, T> } } - /// Return a [Tuple2] of [Option] from a `Option>`. + /// Return a record of [Option] from a `Option>`. /// /// The value on the left of the [Either] will be the first value of the tuple, /// while the right value of the [Either] will be the second of the tuple. From 8759900eb240e7bc377602213892a94841523987 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 20:07:03 +0200 Subject: [PATCH 29/71] updated CHANGELOG --- packages/fpdart/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 72232fe9..a20481b1 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -16,6 +16,8 @@ - `Task` - `TaskOption` - `TaskEither` +- Removed `Tuple2`, use Dart 3 Records instead (`Tuple2(a, b)` becomes simply `(a, b)` 🎯) ⚠️ + - Updated all internal APIs to use records instead of `Tuple2` - Added conversions helpers from `String` to `num`, `int`, `double`, and `bool` using `Option` and `Either` (both as extension methods on `String` and as functions) - `toNumOption` - `toIntOption` From 67fe8e3c82cdbc0b4f800d78fd193ff379f527fa Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 19 May 2023 20:12:29 +0200 Subject: [PATCH 30/71] updated docs --- examples/managing_imports/README.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/examples/managing_imports/README.md b/examples/managing_imports/README.md index 12814d5e..10b32c03 100644 --- a/examples/managing_imports/README.md +++ b/examples/managing_imports/README.md @@ -1,19 +1,13 @@ # Managing Imports -Naming things is hard. Sometimes, the same name gets used for different things. In Dart, naming conflicts can be mitigated through the use of import prefixes, as well as show and hide operations. This is particularly important when using a package like `fpdart` that provides a lot of classes with common names. +Naming things is hard. Sometimes, the same name gets used for different things. In Dart, naming conflicts can be mitigated through the use of [import prefixes](https://dart.dev/language/libraries#specifying-a-library-prefix), as well as [show and hide operations](https://dart.dev/language/libraries#importing-only-part-of-a-library). -Suppose you decide to use `fpdart` with your Flutter program. You'll quickly discover that `fpdart` uses `State` as a class name, which conflicts with the `State` class in Flutter. +This is particularly important when using a package like `fpdart` that provides a lot of classes with common names. -That's problem 1. +As an example, suppose you decide to use `fpdart` with your Flutter program. You'll quickly discover that `fpdart` uses `State` as a class name, which conflicts with the `State` class in Flutter. -Now also suppose you also choose to use `fpdart`'s `Tuple2` class. That's a lot less likely to conflict with anything, but it's still possible. However, you also decide you need a `Tuple3`. `fpdart` doesn't have one. (And likely never will, thanks to the upcoming records feature.) +The solution is to create an import shim that solves both of these problems. We'll call it `functional.dart`. This shim will import `fpdart`, and re-export the classes we want to use. We can rename `fpdart`'s `State` to `FpState` to avoid the conflict. We can then import `functional.dart` instead of `fpdart`. -However, you found one in the [tuple](https://pub.dev/packages/tuple) package, along with `Tuple4` and `Tuple5`, even though it doesn't have much more than element accessors. Close enough for your application. +`functional.dart` can also hold any other functional programming utilities we want to use. It can also be used to provide or import class extensions and mapping functions between our types and the functional types. -But now, you decide to import `tuple` as well, and you get a naming conflict for `Tuple2`. - -That's problem 2. - -The solution is to create an import shim that solves both of these problems. We'll call it `functional.dart`. This shim will import `fpdart` and `tuple`, and re-export the classes we want to use. And, we can rename `fpdart`'s `State` to `FpState` to avoid the conflict. We can then import `functional.dart` instead of `fpdart` and `tuple`. - -`functional.dart` can also hold any other functional programming utilities we want to use. It can also be used to provide or import class extensions and mapping functions between our types and the functional types. A one-stop shop for functional programming in Dart! +A one-stop shop for functional programming in Dart! From 21289364301753e2fe9eab1fd3dbefadaa2b0346 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 20 May 2023 12:29:22 +0200 Subject: [PATCH 31/71] added iterable and predicate extensions --- .../lib/src/extension/extension.export.dart | 2 + .../lib/src/extension/iterable_extension.dart | 64 +++++++++++++ .../lib/src/extension/list_extension.dart | 90 ------------------- .../src/extension/predicate_extension.dart | 17 ++++ .../extension/predicate_extension_test.dart | 20 +++++ 5 files changed, 103 insertions(+), 90 deletions(-) create mode 100644 packages/fpdart/lib/src/extension/iterable_extension.dart create mode 100644 packages/fpdart/lib/src/extension/predicate_extension.dart create mode 100644 packages/fpdart/test/src/extension/predicate_extension_test.dart diff --git a/packages/fpdart/lib/src/extension/extension.export.dart b/packages/fpdart/lib/src/extension/extension.export.dart index 4f1a2c85..1a247e4a 100644 --- a/packages/fpdart/lib/src/extension/extension.export.dart +++ b/packages/fpdart/lib/src/extension/extension.export.dart @@ -1,6 +1,8 @@ export 'curry_extension.dart'; export 'date_time_extension.dart'; +export 'iterable_extension.dart'; export 'list_extension.dart'; export 'map_extension.dart'; export 'option_extension.dart'; +export 'predicate_extension.dart'; export 'string_extension.dart'; diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart new file mode 100644 index 00000000..a4a1d2d8 --- /dev/null +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -0,0 +1,64 @@ +import 'package:fpdart/src/extension/predicate_extension.dart'; + +import '../option.dart'; + +/// {@template fpdart_iterable_extension_head} +/// Get the first element of the [Iterable]. +/// If the [Iterable] is empty, return [None]. +/// {@endtemplate} + +/// Functional programming functions on a mutable dart [Iterable] using `fpdart`. +extension FpdartOnIterable on Iterable { + /// {@macro fpdart_iterable_extension_head} + /// + /// Same as `firstOption`. + Option get head => isEmpty ? const None() : some(first); + + /// {@macro fpdart_iterable_extension_head} + /// + /// Same as `head`. + Option get firstOption => head; + + /// Get the last element of the [Iterable]. + /// If the [Iterable] is empty, return [None]. + /// + /// **Note**: Because accessing the last element of an [Iterable] requires + /// stepping through all the other elements, `lastOption` **can be slow**. + Option get lastOption => isEmpty ? const None() : some(last); + + /// Return all the elements of a [Iterable] except the first one. + /// If the [Iterable] is empty, return [None]. + Option> get tail => isEmpty ? const None() : some(skip(1)); + + /// Return all the elements of a [Iterable] except the last one. + /// If the [Iterable] is empty, return [None]. + Option> get init => + isEmpty ? const None() : some(take(length - 1)); + + /// Returns the list of those elements that satisfy `test`. + /// + /// Equivalent to `Iterable.where`. + Iterable filter(bool Function(T t) test) => where(test); + + /// Extract all elements **starting from the first** as long as `test` returns `true`. + /// + /// Equivalent to `Iterable.takeWhile`. + Iterable takeWhileLeft(bool Function(T t) test) => takeWhile(test); + + /// Remove all elements **starting from the first** as long as `test` returns `true`. + /// + /// Equivalent to `Iterable.skipWhile`. + Iterable dropWhileLeft(bool Function(T t) test) => skipWhile(test); + + /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] + /// with elements that **satisfy** `test` and second element is the remainder of the [Iterable]. + (Iterable, Iterable) span(bool Function(T t) test) => + (takeWhile(test), skipWhile(test)); + + /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] + /// with elements that **do not satisfy** `test` and second element is the remainder of the [Iterable]. + (Iterable, Iterable) breakI(bool Function(T t) test) { + final notTest = test.negate; + return (takeWhile(notTest), skipWhile(notTest)); + } +} diff --git a/packages/fpdart/lib/src/extension/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart index eb5b1d06..36d5f147 100644 --- a/packages/fpdart/lib/src/extension/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -33,34 +33,6 @@ extension FpdartOnMutableIterable on Iterable { Iterable<(T, B)> zip(Iterable lb) => zipWith((a) => (b) => (a, b))(lb); - /// Get the first element of the list. - /// If the list is empty, return [None]. - /// - /// Same as `firstOption`. - Option get head => isEmpty ? none() : some(first); - - /// Get the first element of the list. - /// If the list is empty, return [None]. - /// - /// Same as `head`. - Option get firstOption => head; - - /// Return all the elements of a list except the first one. - /// If the list is empty, return [None]. - Option> get tail => isEmpty ? none() : some(skip(1)); - - /// Return all the elements of a list except the last one. - /// If the list is empty, return [None]. - Option> get init => isEmpty ? none() : some(take(length - 1)); - - /// Get the last element of the list. - /// If the list is empty, return [None]. - Option get lastOption => isEmpty ? none() : some(last); - - /// Returns the list of those elements that satisfy `predicate`. - Iterable filter(bool Function(T t) predicate) => - foldLeft([], (a, e) => predicate(e) ? [...a, e] : a); - /// Append `l` to this [Iterable]. Iterable plus(Iterable l) => [...this, ...l]; @@ -70,34 +42,6 @@ extension FpdartOnMutableIterable on Iterable { /// Insert element `t` at the beginning of the [Iterable]. Iterable prepend(T t) => [t, ...this]; - /// Extract all elements **starting from the first** as long as `predicate` returns `true`. - Iterable takeWhileLeft(bool Function(T t) predicate) => - foldLeft<(bool, Iterable)>( - (true, []), - (a, e) { - if (!a.$1) { - return a; - } - - final check = predicate(e); - return check ? (check, [...a.$2, e]) : (check, a.$2); - }, - ).$2; - - /// Remove all elements **starting from the first** as long as `predicate` returns `true`. - Iterable dropWhileLeft(bool Function(T t) predicate) => - foldLeft<(bool, Iterable)>( - (true, []), - (a, e) { - if (!a.$1) { - return (a.$1, a.$2.append(e)); - } - - final check = predicate(e); - return check ? (check, a.$2) : (check, a.$2.append(e)); - }, - ).$2; - /// Extract all elements **starting from the last** as long as `predicate` returns `true`. Iterable takeWhileRight(bool Function(T t) predicate) => foldRight<(bool, Iterable)>( @@ -126,40 +70,6 @@ extension FpdartOnMutableIterable on Iterable { }, ).$2; - /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] - /// with elements that **satisfy** `predicate` and second element is the remainder of the [Iterable]. - (Iterable, Iterable) span(bool Function(T t) predicate) => - foldLeft<(bool, (Iterable, Iterable))>( - (true, ([], [])), - (a, e) { - if (!a.$1) { - return (a.$1, (a.$2.$1, a.$2.$2.append(e))); - } - - final check = predicate(e); - return check - ? (check, (a.$2.$1.append(e), a.$2.$2)) - : (check, (a.$2.$1, a.$2.$2.append(e))); - }, - ).$2; - - /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] - /// with elements that **do not satisfy** `predicate` and second element is the remainder of the [Iterable]. - (Iterable, Iterable) breakI(bool Function(T t) predicate) => - foldLeft<(bool, (Iterable, Iterable))>( - (false, ([], [])), - (a, e) { - if (a.$1) { - return (a.$1, (a.$2.$1, a.$2.$2.append(e))); - } - - final check = predicate(e); - return check - ? (check, (a.$2.$1, a.$2.$2.append(e))) - : (check, (a.$2.$1.append(e), a.$2.$2)); - }, - ).$2; - /// Return a record where first element is an [Iterable] with the first `n` elements of this [Iterable], /// and the second element contains the rest of the [Iterable]. (Iterable, Iterable) splitAt(int n) => (take(n), skip(n)); diff --git a/packages/fpdart/lib/src/extension/predicate_extension.dart b/packages/fpdart/lib/src/extension/predicate_extension.dart new file mode 100644 index 00000000..e9f9d4ae --- /dev/null +++ b/packages/fpdart/lib/src/extension/predicate_extension.dart @@ -0,0 +1,17 @@ +extension FpdartOnPredicate on bool Function() { + /// Negate the return value of this function. + /// ```dart + /// bool alwaysTrue() => true; + /// final alwaysFalse = alwaysTrue.negate; + /// ``` + bool get negate => !this(); +} + +extension FpdartOnPredicate1

on bool Function(P) { + /// Negate the return value of this function. + /// ```dart + /// bool isEven(int n) => n % 2 == 0; + /// final isOdd = isEven.negate; + /// ``` + bool Function(P) get negate => (p) => !this(p); +} diff --git a/packages/fpdart/test/src/extension/predicate_extension_test.dart b/packages/fpdart/test/src/extension/predicate_extension_test.dart new file mode 100644 index 00000000..cd55c873 --- /dev/null +++ b/packages/fpdart/test/src/extension/predicate_extension_test.dart @@ -0,0 +1,20 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:glados/glados.dart'; + +void main() { + group('Predicate extension', () { + group('FpdartOnPredicate', () { + Glados(any.bool).test('negate', (boolValue) { + bool fun() => boolValue; + expect(fun(), !fun.negate); + }); + }); + + group('FpdartOnPredicate1', () { + Glados(any.int).test('negate', (intValue) { + bool fun(int n) => n % 2 == 0; + expect(fun(intValue), !fun.negate(intValue)); + }); + }); + }); +} From 6ff635778ec8fb1d62a40bec0ff15ac26f54a42b Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 20 May 2023 12:59:30 +0200 Subject: [PATCH 32/71] refactoring iterable methods --- packages/fpdart/CHANGELOG.md | 11 +++ .../lib/src/extension/iterable_extension.dart | 71 +++++++++++++++++-- .../lib/src/extension/list_extension.dart | 63 +--------------- 3 files changed, 81 insertions(+), 64 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index a20481b1..c8e33714 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -18,6 +18,17 @@ - `TaskEither` - Removed `Tuple2`, use Dart 3 Records instead (`Tuple2(a, b)` becomes simply `(a, b)` 🎯) ⚠️ - Updated all internal APIs to use records instead of `Tuple2` +- Major refactoring of `Iterable` and `List` extension methods + - Improved performance + - Correct return types (`Iterable` and `List`) ([#65](https://github.com/SandroMaglione/fpdart/issues/65)) + - Added the following methods + - `concat` (`Iterable`) + - Renamed the following methods ⚠️ + - `concat` → `flatten` (on `Iterable>`) + - Removed the following methods ⚠️ + - `breakI` (use `partition` instead) + - `concatMap` (use `flatMap` instead) + - `bind` (use `flatMap` instead) - Added conversions helpers from `String` to `num`, `int`, `double`, and `bool` using `Option` and `Either` (both as extension methods on `String` and as functions) - `toNumOption` - `toIntOption` diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index a4a1d2d8..3d75875a 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -1,6 +1,4 @@ -import 'package:fpdart/src/extension/predicate_extension.dart'; - -import '../option.dart'; +import 'package:fpdart/fpdart.dart'; /// {@template fpdart_iterable_extension_head} /// Get the first element of the [Iterable]. @@ -57,8 +55,73 @@ extension FpdartOnIterable on Iterable { /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] /// with elements that **do not satisfy** `test` and second element is the remainder of the [Iterable]. - (Iterable, Iterable) breakI(bool Function(T t) test) { + (Iterable, Iterable) partition(bool Function(T t) test) { final notTest = test.negate; return (takeWhile(notTest), skipWhile(notTest)); } + + /// Return a record where first element is an [Iterable] with the first `n` elements of this [Iterable], + /// and the second element contains the rest of the [Iterable]. + (Iterable, Iterable) splitAt(int n) => (take(n), skip(n)); + + /// Return the suffix of this [Iterable] after the first `n` elements. + /// + /// Equivalent to `Iterable.skip`. + Iterable drop(int n) => skip(n); + + /// Checks whether every element of this [Iterable] satisfies [test]. + /// + /// Equivalent to `Iterable.every`. + bool all(bool Function(T t) test) => every(test); + + /// Creates the lazy concatenation of this [Iterable] and `other`. + /// + /// Equivalent to `Iterable.followedBy`. + Iterable concat(Iterable other) => followedBy(other); + + /// Check if `element` is contained inside this [Iterable]. + /// + /// Equivalent to `Iterable.contains`. + bool elem(T element) => contains(element); + + /// Check if `element` is **not** contained inside this [Iterable]. + bool notElem(T element) => !elem(element); + + /// Fold a [List] into a single value by aggregating each element of the list + /// **from the first to the last**. + /// + /// Equivalent to `Iterable.fold`. + B foldLeft(B initialValue, B Function(B b, T t) combine) => + fold(initialValue, combine); + + /// For each element of the [Iterable] apply function `toElements` and flat the result. + /// + /// Equivalent to `Iterable.expand`. + Iterable flatMap(Iterable Function(T t) toElements) => + expand(toElements); + + /// Join elements at the same index from two different [Iterable] into + /// one [Iterable] containing the result of calling `combine` on + /// each element pair. + Iterable zipWith( + C Function(T t, B b) combine, + Iterable iterableB, + ) { + final iteratorB = iterableB.iterator; + if (!iteratorB.moveNext()) return []; + + final resultList = []; + for (T elementT in this) { + resultList.add(combine(elementT, iteratorB.current)); + if (!iteratorB.moveNext()) break; + } + + return resultList; + } +} + +/// Functional programming functions on `Iterable>` using `fpdart`. +extension FpdartOnIterableOfIterable on Iterable> { + /// From a `Iterable>` return a `Iterable` of their concatenation. + Iterable get flatten => expand(identity); } diff --git a/packages/fpdart/lib/src/extension/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart index 36d5f147..691b51bd 100644 --- a/packages/fpdart/lib/src/extension/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -10,7 +10,7 @@ import '../task_option.dart'; import '../typeclass/order.dart'; /// Functional programming functions on a mutable dart [Iterable] using `fpdart`. -extension FpdartOnMutableIterable on Iterable { +extension FpdartOnMutableIterable on List { /// Join elements at the same index from two different [List] into /// one [List] containing the result of calling `f` on the elements pair. Iterable Function(Iterable lb) zipWith( @@ -70,18 +70,6 @@ extension FpdartOnMutableIterable on Iterable { }, ).$2; - /// Return a record where first element is an [Iterable] with the first `n` elements of this [Iterable], - /// and the second element contains the rest of the [Iterable]. - (Iterable, Iterable) splitAt(int n) => (take(n), skip(n)); - - /// Check if `element` is contained inside this [Iterable]. - /// - /// Same as standard dart `contains`. - bool elem(T element) => contains(element); - - /// Check if `element` is **not** contained inside this [Iterable]. - bool notElem(T element) => !elem(element); - /// Insert `element` into the list at the first position where it is less than or equal to the next element /// based on `order`. /// @@ -104,9 +92,8 @@ extension FpdartOnMutableIterable on Iterable { ? [first, ...drop(1).insertWith(insert, order, element)] : [element, first, ...drop(1)]; - /// Sort this [Iterable] based on `order`. - Iterable sortBy(Order order) => - [...this]..sort((x, y) => order.compare(x, y)); + /// Sort this [List] based on `order`. + List sortBy(Order order) => [...this]..sort(order.compare); /// Sort this [Iterable] based on `order` of an object of type `A` extracted from `T` using `sort`. Iterable sortWith(A Function(T instance) sort, Order order) => @@ -150,23 +137,6 @@ extension FpdartOnMutableIterable on Iterable { ), )); - /// Checks whether every element of this [Iterable] satisfies [test]. - /// - /// Same as standard dart `every`. - bool all(bool Function(T t) predicate) => every(predicate); - - /// Return the suffix of this [Iterable] after the first `n` elements. - /// - /// Same as standard dart `skip`. - Iterable drop(int n) => skip(n); - - /// Fold a [List] into a single value by aggregating each element of the list - /// **from the first to the last**. - /// - /// Same as standard `fold`. - B foldLeft(B initialValue, B Function(B b, T t) f) => - fold(initialValue, f); - /// Fold a [List] into a single value by aggregating each element of the list /// **from the first to the last** using their index. B foldLeftWithIndex( @@ -198,11 +168,6 @@ extension FpdartOnMutableIterable on Iterable { Iterable ap(Iterable fl) => fl.concatMap((f) => map(f)); - /// Apply `f` to each element of the [Iterable] and flat the result using `concat`. - /// - /// Same as `bind` and `flatMap`. - Iterable concatMap(Iterable Function(T t) f) => map(f).concat; - /// Apply `f` to each element of the [Iterable] using the index /// and flat the result using `concat`. /// @@ -210,34 +175,18 @@ extension FpdartOnMutableIterable on Iterable { Iterable concatMapWithIndex(Iterable Function(T t, int index) f) => mapWithIndex(f).concat; - /// For each element of the [Iterable] apply function `f` and flat the result. - /// - /// Same as `bind` and `concatMap`. - Iterable flatMap(Iterable Function(T t) f) => concatMap(f); - /// For each element of the [Iterable] apply function `f` with the index and flat the result. /// /// Same as `bindWithIndex` and `concatMapWithIndex`. Iterable flatMapWithIndex(Iterable Function(T t, int index) f) => concatMapWithIndex(f); - /// For each element of the [Iterable] apply function `f` and flat the result. - /// - /// Same as `flatMap` and `concatMap`. - Iterable bind(Iterable Function(T t) f) => flatMap(f); - /// For each element of the [Iterable] apply function `f` with the index and flat the result. /// /// Same as `flatMapWithIndex` and `concatMapWithIndex`. Iterable bindWithIndex(Iterable Function(T t, int index) f) => concatMapWithIndex(f); - /// Return a record where the first element is an [Iterable] with all the elements - /// of this [Iterable] that do not satisfy `f` and the second all the elements that - /// do satisfy f. - (Iterable, Iterable) partition(bool Function(T t) f) => - (filter((t) => !f(t)), filter(f)); - /// Sort [Iterable] based on [DateTime] extracted from type `T` using `getDate`. /// /// Sorting [DateTime] in **ascending** order (older dates first). @@ -245,12 +194,6 @@ extension FpdartOnMutableIterable on Iterable { sortWith(getDate, dateOrder); } -/// Functional programming functions on a mutable dart `Iterable>` using `fpdart`. -extension FpdartOnMutableIterableOfIterable on Iterable> { - /// From a container of `Iterable>` return a `Iterable` of their concatenation. - Iterable get concat => foldRight([], (a, e) => [...a, ...e]); -} - extension FpdartTraversableIterable on Iterable { /// {@macro fpdart_traverse_list_option} Option> traverseOptionWithIndex( From f8cfd3a71b307d25c298936d0675636ef7e83f27 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 20 May 2023 18:44:10 +0200 Subject: [PATCH 33/71] moved and refactored iterable extension methods --- packages/fpdart/CHANGELOG.md | 9 +- .../lib/src/extension/iterable_extension.dart | 238 +++++++++++++++++- .../lib/src/extension/list_extension.dart | 201 ++------------- .../lib/src/extension/map_extension.dart | 2 +- 4 files changed, 256 insertions(+), 194 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index c8e33714..1e18257a 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -22,13 +22,20 @@ - Improved performance - Correct return types (`Iterable` and `List`) ([#65](https://github.com/SandroMaglione/fpdart/issues/65)) - Added the following methods - - `concat` (`Iterable`) + - `intersectBy` (`Iterable`) + - `intersectWith` (`Iterable`) + - Updated the following methods ⚠️ + - `foldRight`, `foldRightWithIndex` (`List`): Changed parameter order in `combine` function + - `toIterable` changed to `toList` (`Map`) - Renamed the following methods ⚠️ + - `plus` → `concat` (`Iterable`) - `concat` → `flatten` (on `Iterable>`) - Removed the following methods ⚠️ - `breakI` (use `partition` instead) - `concatMap` (use `flatMap` instead) - `bind` (use `flatMap` instead) + - `bindWithIndex` (use `flatMapWithIndex` instead) + - `concatMapWithIndex` (use `flatMapWithIndex` instead) - Added conversions helpers from `String` to `num`, `int`, `double`, and `bool` using `Option` and `Either` (both as extension methods on `String` and as functions) - `toNumOption` - `toIntOption` diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index 3d75875a..924ba8fa 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -1,4 +1,9 @@ -import 'package:fpdart/fpdart.dart'; +import '../date.dart'; +import '../function.dart'; +import '../option.dart'; +import '../typeclass/eq.dart'; +import '../typeclass/order.dart'; +import 'predicate_extension.dart'; /// {@template fpdart_iterable_extension_head} /// Get the first element of the [Iterable]. @@ -79,6 +84,18 @@ extension FpdartOnIterable on Iterable { /// Equivalent to `Iterable.followedBy`. Iterable concat(Iterable other) => followedBy(other); + /// Insert `element` at the beginning of the [Iterable]. + Iterable prepend(T element) sync* { + yield element; + yield* this; + } + + /// Insert `element` at the end of the [Iterable]. + Iterable append(T element) sync* { + yield* this; + yield element; + } + /// Check if `element` is contained inside this [Iterable]. /// /// Equivalent to `Iterable.contains`. @@ -87,13 +104,28 @@ extension FpdartOnIterable on Iterable { /// Check if `element` is **not** contained inside this [Iterable]. bool notElem(T element) => !elem(element); - /// Fold a [List] into a single value by aggregating each element of the list + /// Fold this [Iterable] into a single value by aggregating each element of the list /// **from the first to the last**. /// /// Equivalent to `Iterable.fold`. B foldLeft(B initialValue, B Function(B b, T t) combine) => fold(initialValue, combine); + /// Same as `foldLeft` (`fold`) but provides also the `index` of each mapped + /// element in the `combine` function. + B foldLeftWithIndex( + B initialValue, + B Function(B previousValue, T element, int index) combine, + ) { + var index = 0; + var value = initialValue; + for (var element in this) { + value = combine(value, element, index); + index += 1; + } + return value; + } + /// For each element of the [Iterable] apply function `toElements` and flat the result. /// /// Equivalent to `Iterable.expand`. @@ -103,21 +135,205 @@ extension FpdartOnIterable on Iterable { /// Join elements at the same index from two different [Iterable] into /// one [Iterable] containing the result of calling `combine` on /// each element pair. + /// + /// If one input [Iterable] is shorter, + /// excess elements of the longer [Iterable] are discarded. Iterable zipWith( C Function(T t, B b) combine, - Iterable iterableB, - ) { - final iteratorB = iterableB.iterator; - if (!iteratorB.moveNext()) return []; + Iterable iterable, + ) sync* { + if (isNotEmpty && iterable.isNotEmpty) { + yield combine(first, iterable.first); + yield* skip(1).zipWith( + combine, + iterable.skip(1), + ); + } + } + + /// `zip` is used to join elements at the same index from two different [Iterable] + /// into one [Iterable] of a record. + /// ```dart + /// final list1 = ['a', 'b']; + /// final list2 = [1, 2]; + /// final zipList = list1.zip(list2); + /// print(zipList); // -> [(a, 1), (b, 2)] + /// ``` + Iterable<(T, B)> zip(Iterable iterable) => + zipWith((a, b) => (a, b), iterable); + + /// Insert `element` into the list at the first position where it is less than or equal to the next element + /// based on `order` ([Order]). + /// + /// Note: The element is added **before** an equal element already in the [Iterable]. + Iterable insertBy(Order order, T element) sync* { + if (isEmpty) { + yield element; + } else { + if (order.compare(element, first) > 0) { + yield first; + yield* skip(1).insertBy(order, element); + } else { + yield element; + yield* this; + } + } + } + + /// Insert `element` into the [Iterable] at the first position where + /// it is less than or equal to the next element + /// based on `order` ([Order]). + /// + /// `order` refers to values of type `A` + /// extracted from `element` using `extract`. + /// + /// Note: The element is added **before** an equal element already in the [Iterable]. + Iterable insertWith( + A Function(T instance) extract, + Order order, + T element, + ) sync* { + if (isEmpty) { + yield element; + } else { + if (order.compare(extract(element), extract(first)) > 0) { + yield first; + yield* skip(1).insertWith(extract, order, element); + } else { + yield element; + yield* this; + } + } + } + + /// Remove the **first occurrence** of `element` from this [Iterable]. + Iterable delete(T element) sync* { + if (isNotEmpty) { + if (first != element) { + yield first; + } - final resultList = []; - for (T elementT in this) { - resultList.add(combine(elementT, iteratorB.current)); - if (!iteratorB.moveNext()) break; + yield* skip(1).delete(element); } + } - return resultList; + /// Same as `map` but provides also the `index` of each mapped + /// element in the mapping function (`toElement`). + Iterable mapWithIndex(B Function(T t, int index) toElement) sync* { + var index = 0; + for (var value in this) { + yield toElement(value, index); + index += 1; + } + } + + /// Same as `flatMap` (`extend`) but provides also the `index` of each mapped + /// element in the mapping function (`toElements`). + Iterable flatMapWithIndex( + Iterable Function(T t, int index) toElements, + ) sync* { + var index = 0; + for (var value in this) { + yield* toElements(value, index); + index += 1; + } } + + /// The largest element of this [Iterable] based on `order`. + /// + /// If the list is empty, return [None]. + Option maximumBy(Order order) { + if (isEmpty) return const None(); + + var max = first; + for (var element in skip(1)) { + if (order.compare(element, max) > 0) { + max = element; + } + } + + return some(max); + } + + /// The least element of this [Iterable] based on `order`. + /// + /// If the list is empty, return [None]. + Option minimumBy(Order order) { + if (isEmpty) return const None(); + + var min = first; + for (var element in skip(1)) { + if (order.compare(element, min) < 0) { + min = element; + } + } + + return some(min); + } + + /// Apply all the functions inside `iterable` to this [Iterable]. + Iterable ap(Iterable iterable) => iterable.flatMap(map); + + /// Return the intersection of two [Iterable] (all the elements that both [Iterable] have in common). + Iterable intersect(Iterable iterable) sync* { + for (var element in this) { + try { + final e = iterable.firstWhere((e) => e == element); + yield e; + } catch (_) { + continue; + } + } + } + + /// Return the intersection of two [Iterable] (all the elements that both [Iterable] have in common) + /// based on `eq` to check equality. + Iterable intersectBy(Eq eq, Iterable iterable) sync* { + for (var element in this) { + try { + final e = iterable.firstWhere( + (e) => eq.eqv(e, element), + ); + yield e; + } catch (_) { + continue; + } + } + } + + /// Return the intersection of two [Iterable] (all the elements that both [Iterable] have in common) + /// based on `eq` to check equality. + /// + /// `eq` refers to a value of type `A` extracted from `T` using `extract`. + Iterable intersectWith( + A Function(T t) extract, + Eq eq, + Iterable iterable, + ) sync* { + for (var element in this) { + try { + final e = iterable.firstWhere( + (e) => eq.eqv(extract(e), extract(element)), + ); + yield e; + } catch (_) { + continue; + } + } + } + + /// Sort this [List] based on `order` ([Order]). + List sortBy(Order order) => [...this]..sort(order.compare); + + /// Sort this [Iterable] based on `order` of an object of type `A` extracted from `T` using `extract`. + List sortWith(A Function(T t) extract, Order order) => + [...this]..sort((e1, e2) => order.compare(extract(e1), extract(e2))); + + /// Sort this [Iterable] based on [DateTime] extracted from type `T` using `getDate`. + /// + /// Sorting [DateTime] in **ascending** order (older dates first). + List sortWithDate(DateTime Function(T instance) getDate) => + sortWith(getDate, dateOrder); } /// Functional programming functions on `Iterable>` using `fpdart`. diff --git a/packages/fpdart/lib/src/extension/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart index 691b51bd..91960135 100644 --- a/packages/fpdart/lib/src/extension/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -1,4 +1,3 @@ -import '../date.dart'; import '../either.dart'; import '../io.dart'; import '../io_either.dart'; @@ -7,191 +6,31 @@ import '../option.dart'; import '../task.dart'; import '../task_either.dart'; import '../task_option.dart'; -import '../typeclass/order.dart'; /// Functional programming functions on a mutable dart [Iterable] using `fpdart`. extension FpdartOnMutableIterable on List { - /// Join elements at the same index from two different [List] into - /// one [List] containing the result of calling `f` on the elements pair. - Iterable Function(Iterable lb) zipWith( - C Function(B b) Function(T t) f) => - (Iterable lb) => isEmpty || lb.isEmpty - ? [] - : [ - f(elementAt(0))(lb.elementAt(0)), - ...skip(1).zipWith(f)(lb.skip(1)), - ]; - - /// `zip` is used to join elements at the same index from two different [List] - /// into one [List] of a record. - /// ```dart - /// final list1 = ['a', 'b']; - /// final list2 = [1, 2]; - /// final zipList = list1.zip(list2); - /// print(zipList); // -> [(a, 1), (b, 2)] - /// ``` - Iterable<(T, B)> zip(Iterable lb) => - zipWith((a) => (b) => (a, b))(lb); - - /// Append `l` to this [Iterable]. - Iterable plus(Iterable l) => [...this, ...l]; - - /// Insert element `t` at the end of the [Iterable]. - Iterable append(T t) => [...this, t]; - - /// Insert element `t` at the beginning of the [Iterable]. - Iterable prepend(T t) => [t, ...this]; - - /// Extract all elements **starting from the last** as long as `predicate` returns `true`. - Iterable takeWhileRight(bool Function(T t) predicate) => - foldRight<(bool, Iterable)>( - const (true, []), - (e, a) { - if (!a.$1) { - return a; - } - - final check = predicate(e); - return check ? (check, a.$2.prepend(e)) : (check, a.$2); - }, - ).$2; - - /// Remove all elements **starting from the last** as long as `predicate` returns `true`. - Iterable dropWhileRight(bool Function(T t) predicate) => - foldRight<(bool, Iterable)>( - const (true, []), - (e, a) { - if (!a.$1) { - return (a.$1, a.$2.prepend(e)); - } - - final check = predicate(e); - return check ? (check, a.$2) : (check, a.$2.prepend(e)); - }, - ).$2; - - /// Insert `element` into the list at the first position where it is less than or equal to the next element - /// based on `order`. - /// - /// Note: The element is added **before** an equal element already in the [Iterable]. - Iterable insertBy(Order order, T element) => isEmpty - ? [element] - : order.compare(element, first) > 0 - ? [first, ...drop(1).insertBy(order, element)] - : [element, first, ...drop(1)]; - - /// Insert `element` into the list at the first position where it is less than or equal to the next element - /// based on `order` of an object of type `A` extracted from `element` using `insert`. - /// - /// Note: The element is added **before** an equal element already in the [Iterable]. - Iterable insertWith( - A Function(T instance) insert, Order order, T element) => - isEmpty - ? [element] - : order.compare(insert(element), insert(first)) > 0 - ? [first, ...drop(1).insertWith(insert, order, element)] - : [element, first, ...drop(1)]; - - /// Sort this [List] based on `order`. - List sortBy(Order order) => [...this]..sort(order.compare); - - /// Sort this [Iterable] based on `order` of an object of type `A` extracted from `T` using `sort`. - Iterable sortWith(A Function(T instance) sort, Order order) => - foldRight([], (e, a) => a.insertWith(sort, order, e)); - - /// Return the intersection of two [Iterable] (all the elements that both [Iterable] have in common). - Iterable intersect(Iterable l) => - foldLeft([], (a, e) => l.elem(e) ? [...a, e] : a); - - /// Remove the **first occurrence** of `element` from this [Iterable]. - Iterable delete(T element) => - foldLeft<(bool, Iterable)>((true, []), (a, e) { - if (!a.$1) { - return (a.$1, a.$2.append(e)); - } - - return e == element ? (false, a.$2) : (a.$1, a.$2.append(e)); - }).$2; - - /// The largest element of this [Iterable] based on `order`. - /// - /// If the list is empty, return [None]. - Option maximumBy(Order order) => foldLeft( - none(), - (a, c) => some( - a.match( - () => c, - (t) => order.compare(c, t) > 0 ? c : t, - ), - )); - - /// The least element of this [Iterable] based on `order`. - /// - /// If the list is empty, return [None]. - Option minimumBy(Order order) => foldLeft( - none(), - (a, c) => some( - a.match( - () => c, - (t) => order.compare(c, t) < 0 ? c : t, - ), - )); - - /// Fold a [List] into a single value by aggregating each element of the list - /// **from the first to the last** using their index. - B foldLeftWithIndex( - B initialValue, B Function(B accumulator, T element, int index) f) => - fold<(B, int)>( - (initialValue, 0), - (p, e) => (f(p.$1, e, p.$2), p.$2 + 1), - ).$1; - - /// Fold a [List] into a single value by aggregating each element of the list - /// **from the last to the first**. - B foldRight(B initialValue, B Function(T element, B accumulator) f) => - toList().reversed.fold(initialValue, (a, e) => f(e, a)); - - /// Fold a [List] into a single value by aggregating each element of the list + /// Fold this [List] into a single value by aggregating each element of the list /// **from the last to the first** using their index. B foldRightWithIndex( - B initialValue, B Function(T element, B accumulator, int index) f) => - foldRight<(B, int)>( - (initialValue, 0), - (e, p) => (f(e, p.$1, p.$2), p.$2 + 1), - ).$1; - - /// Map [Iterable] from type `T` to type `B` using the index. - Iterable mapWithIndex(B Function(T t, int index) f) => - foldLeftWithIndex([], (a, e, i) => [...a, f(e, i)]); - - /// Apply all the functions inside `fl` to the [Iterable]. - Iterable ap(Iterable fl) => - fl.concatMap((f) => map(f)); - - /// Apply `f` to each element of the [Iterable] using the index - /// and flat the result using `concat`. - /// - /// Same as `bindWithIndex` and `flatMapWithIndex`. - Iterable concatMapWithIndex(Iterable Function(T t, int index) f) => - mapWithIndex(f).concat; - - /// For each element of the [Iterable] apply function `f` with the index and flat the result. - /// - /// Same as `bindWithIndex` and `concatMapWithIndex`. - Iterable flatMapWithIndex(Iterable Function(T t, int index) f) => - concatMapWithIndex(f); - - /// For each element of the [Iterable] apply function `f` with the index and flat the result. - /// - /// Same as `flatMapWithIndex` and `concatMapWithIndex`. - Iterable bindWithIndex(Iterable Function(T t, int index) f) => - concatMapWithIndex(f); - - /// Sort [Iterable] based on [DateTime] extracted from type `T` using `getDate`. - /// - /// Sorting [DateTime] in **ascending** order (older dates first). - Iterable sortWithDate(DateTime Function(T instance) getDate) => - sortWith(getDate, dateOrder); + B initialValue, + B Function(B previousValue, T element, int index) combine, + ) { + var index = 0; + var value = initialValue; + for (var element in reversed) { + value = combine(value, element, index); + index += 1; + } + return value; + } + + /// Extract all elements **starting from the last** as long as `test` returns `true`. + Iterable takeWhileRight(bool Function(T t) test) => + reversed.takeWhile(test); + + /// Remove all elements **starting from the last** as long as `test` returns `true`. + Iterable dropWhileRight(bool Function(T t) test) => + reversed.skipWhile(test); } extension FpdartTraversableIterable on Iterable { diff --git a/packages/fpdart/lib/src/extension/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart index 1ac50fbb..d31a6686 100644 --- a/packages/fpdart/lib/src/extension/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -2,7 +2,7 @@ import '../option.dart'; import '../typeclass/eq.dart'; import '../typeclass/order.dart'; import '../typedef.dart'; -import 'list_extension.dart'; +import 'iterable_extension.dart'; import 'option_extension.dart'; /// Functional programming functions on a mutable dart [Map] using `fpdart`. From 4d4146291469f5a0d4f05aefbcc9cf6f16821646 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 20 May 2023 19:16:15 +0200 Subject: [PATCH 34/71] fixed list and iterable tests --- packages/fpdart/CHANGELOG.md | 6 +- packages/fpdart/example/main.dart | 2 +- .../lib/src/extension/iterable_extension.dart | 5 +- .../lib/src/extension/list_extension.dart | 17 +- packages/fpdart/lib/src/task.dart | 2 +- .../extension/iterable_extension_test.dart | 1583 ++++++++++++++++ .../src/extension/list_extension_test.dart | 1616 +---------------- 7 files changed, 1623 insertions(+), 1608 deletions(-) create mode 100644 packages/fpdart/test/src/extension/iterable_extension_test.dart diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 1e18257a..41739be7 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -24,9 +24,13 @@ - Added the following methods - `intersectBy` (`Iterable`) - `intersectWith` (`Iterable`) + - Fixed the following methods ⚠️ + - `takeWhileRight`: Result `List` reversed + - `dropWhileRight`: Result `List` reversed + - `partition`, `span`: Returns longest **prefix** (and not all elements matching `test`) - Updated the following methods ⚠️ - `foldRight`, `foldRightWithIndex` (`List`): Changed parameter order in `combine` function - - `toIterable` changed to `toList` (`Map`) + - `zipWith` (`Iterable`): Changed parameters definition, no more curried - Renamed the following methods ⚠️ - `plus` → `concat` (`Iterable`) - `concat` → `flatten` (on `Iterable>`) diff --git a/packages/fpdart/example/main.dart b/packages/fpdart/example/main.dart index 10a27982..b046fff2 100644 --- a/packages/fpdart/example/main.dart +++ b/packages/fpdart/example/main.dart @@ -50,7 +50,7 @@ void imperativeVSfunctional() { /// Is it even possible? 🤷‍♂️ final result = list .where((e) => e > 2) - .plus([1, 2, 3]) + .concat([1, 2, 3]) .drop(2) .intersect([1, 2, 3]) .map((e) => e * 2) diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index 924ba8fa..49a6caee 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -211,9 +211,10 @@ extension FpdartOnIterable on Iterable { if (isNotEmpty) { if (first != element) { yield first; + yield* skip(1).delete(element); + } else { + yield* skip(1); } - - yield* skip(1).delete(element); } } diff --git a/packages/fpdart/lib/src/extension/list_extension.dart b/packages/fpdart/lib/src/extension/list_extension.dart index 91960135..45c3b2af 100644 --- a/packages/fpdart/lib/src/extension/list_extension.dart +++ b/packages/fpdart/lib/src/extension/list_extension.dart @@ -8,9 +8,22 @@ import '../task_either.dart'; import '../task_option.dart'; /// Functional programming functions on a mutable dart [Iterable] using `fpdart`. -extension FpdartOnMutableIterable on List { +extension FpdartOnList on List { /// Fold this [List] into a single value by aggregating each element of the list - /// **from the last to the first** using their index. + /// **from the last to the first**. + B foldRight( + B initialValue, + B Function(B previousValue, T element) combine, + ) { + var value = initialValue; + for (var element in reversed) { + value = combine(value, element); + } + return value; + } + + /// Same as `foldRight` but provides also the `index` of each mapped + /// element in the `combine` function. B foldRightWithIndex( B initialValue, B Function(B previousValue, T element, int index) combine, diff --git a/packages/fpdart/lib/src/task.dart b/packages/fpdart/lib/src/task.dart index d29d9171..12dce592 100644 --- a/packages/fpdart/lib/src/task.dart +++ b/packages/fpdart/lib/src/task.dart @@ -1,5 +1,5 @@ import 'either.dart'; -import 'extension/list_extension.dart'; +import 'extension/iterable_extension.dart'; import 'function.dart'; import 'option.dart'; import 'task_either.dart'; diff --git a/packages/fpdart/test/src/extension/iterable_extension_test.dart b/packages/fpdart/test/src/extension/iterable_extension_test.dart new file mode 100644 index 00000000..5a223348 --- /dev/null +++ b/packages/fpdart/test/src/extension/iterable_extension_test.dart @@ -0,0 +1,1583 @@ +import 'package:fpdart/fpdart.dart'; + +import '../utils/utils.dart'; + +/// Used to test sorting with [DateTime] (`sortWithDate`) +class SortDate { + final int id; + final DateTime date; + const SortDate(this.id, this.date); +} + +void main() { + /// Check if two [Iterable] have the same element in the same order + bool eq(Iterable a, Iterable b) => a.foldLeftWithIndex( + false, + (a, e, i) => e == b.elementAt(i), + ); + + group('FpdartOnList', () { + test('zipWith', () { + final list1 = [1, 2]; + final list2 = ['a', 'b']; + final result = list1.zipWith( + (t, i) => (t + i.length) / 2, + list2, + ); + + expect(eq(result, [1.0, 1.5]), true); + }); + + test('zip', () { + final list1 = [1, 2]; + final list2 = ['a', 'b']; + final ap = list1.zip(list2); + + expect(eq(ap, [(1, 'a'), (2, 'b')]), true); + }); + + test('filter', () { + final list1 = [1, 2, 3, 4, 5, 6]; + final ap = list1.filter((t) => t > 3); + + expect(eq(ap, [4, 5, 6]), true); + }); + + test('concat', () { + final list1 = [1, 2, 3, 4, 5, 6]; + final ap = list1.concat([7, 8]); + + expect(eq(ap, [1, 2, 3, 4, 5, 6, 7, 8]), true); + }); + + test('append', () { + final list1 = [1, 2, 3, 4, 5, 6]; + final ap = list1.append(7); + + expect(eq(ap, [1, 2, 3, 4, 5, 6, 7]), true); + }); + + test('prepend', () { + final list1 = [1, 2, 3, 4, 5, 6]; + final ap = list1.prepend(0); + + expect(eq(ap, [0, 1, 2, 3, 4, 5, 6]), true); + }); + + test('insertBy', () { + final list1 = [1, 2, 3, 4, 5, 6]; + final ap = list1.insertBy(Order.from((a1, a2) => a1.compareTo(a2)), 4); + + expect(eq(ap, [1, 2, 3, 4, 4, 5, 6]), true); + }); + + test('insertWith', () { + final list1 = [ + SortDate(2, DateTime(2019)), + SortDate(4, DateTime(2017)), + SortDate(1, DateTime(2020)), + SortDate(3, DateTime(2018)), + ]; + final ap = list1.insertWith( + (instance) => instance.date, + dateOrder, + SortDate(5, DateTime(2021)), + ); + + expect(ap.elementAt(4).id, 5); + expect(ap.elementAt(4).date.year, 2021); + }); + + test('sortBy', () { + final list1 = [2, 6, 4, 1, 5, 3]; + final ap = list1.sortBy(Order.from((a1, a2) => a1.compareTo(a2))); + + expect(eq(ap, [1, 2, 3, 4, 5, 6]), true); + }); + + test('sortWith', () { + final list1 = [ + SortDate(2, DateTime(2019)), + SortDate(4, DateTime(2017)), + SortDate(1, DateTime(2020)), + SortDate(3, DateTime(2018)), + ]; + final ap = list1.sortWith((instance) => instance.date, dateOrder); + + expect(ap.elementAt(0).id, 4); + expect(ap.elementAt(1).id, 3); + expect(ap.elementAt(2).id, 2); + expect(ap.elementAt(3).id, 1); + }); + + test('sortWithDate', () { + final list1 = [ + SortDate(2, DateTime(2019)), + SortDate(4, DateTime(2017)), + SortDate(1, DateTime(2020)), + SortDate(3, DateTime(2018)), + ]; + final ap = list1.sortWithDate((instance) => instance.date); + + expect(ap.elementAt(0).date.year, 2017); + expect(ap.elementAt(1).date.year, 2018); + expect(ap.elementAt(2).date.year, 2019); + expect(ap.elementAt(3).date.year, 2020); + }); + + test('sortBy', () { + final list1 = [2, 6, 4, 1, 5, 3]; + final ap = list1.sortBy(Order.from((a1, a2) => a1.compareTo(a2))); + + expect(eq(ap, [1, 2, 3, 4, 5, 6]), true); + }); + + test('intersect', () { + final list1 = [1, 2, 3, 4, 5, 6]; + final ap = list1.intersect([1, 2, 3, 10, 11, 12]); + + expect(eq(ap, [1, 2, 3]), true); + }); + + group('head', () { + test('Some', () { + final list1 = [1, 2]; + final ap = list1.head; + expect(ap, isA()); + expect(ap.getOrElse(() => -1), 1); + }); + + test('None', () { + final List list1 = []; + final ap = list1.head; + expect(ap, isA()); + expect(ap.getOrElse(() => -1), -1); + }); + }); + + group('firstOption', () { + test('Some', () { + final list1 = [1, 2]; + final ap = list1.firstOption; + expect(ap, isA()); + expect(ap.getOrElse(() => -1), 1); + }); + }); + + group('tail', () { + test('Some', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.tail; + expect(ap, isA()); + expect(ap.getOrElse(() => []), [2, 3, 4]); + }); + }); + + group('init', () { + test('Some', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.init; + expect(ap, isA()); + expect(ap.getOrElse(() => []), [1, 2, 3]); + }); + }); + + group('lastOption', () { + test('Some', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.lastOption; + expect(ap, isA()); + expect(ap.getOrElse(() => -1), 4); + }); + }); + + test('takeWhileLeft', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.takeWhileLeft((t) => t < 3); + expect(eq(ap, [1, 2]), true); + }); + + test('dropWhileLeft', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.dropWhileLeft((t) => t < 3); + expect(eq(ap, [3, 4]), true); + }); + + test('span', () { + final list1 = [1, 5, 2, 3, 4]; + final ap = list1.span((t) => t < 3); + expect(ap.$1.length, 1); + expect(ap.$1.elementAt(0), 1); + + expect(ap.$2.length, 4); + expect(ap.$2.elementAt(0), 5); + expect(ap.$2.elementAt(1), 2); + expect(ap.$2.elementAt(2), 3); + expect(ap.$2.elementAt(3), 4); + }); + + test('splitAt', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.splitAt(2); + expect(eq(ap.$1, [1, 2]), true); + expect(eq(ap.$2, [3, 4]), true); + }); + + test('delete', () { + final list1 = [1, 2, 3, 2]; + final ap = list1.delete(2); + expect(ap.length, 3); + expect(ap.elementAt(0), 1); + expect(ap.elementAt(1), 3); + expect(ap.elementAt(2), 2); + }); + + test('maximumBy', () { + final list1 = [2, 5, 4, 6, 1, 3]; + final ap = list1.maximumBy(Order.from((a1, a2) => a1.compareTo(a2))); + expect(ap.getOrElse(() => -1), 6); + }); + + test('minimumBy', () { + final list1 = [2, 5, 4, 6, 1, 3]; + final ap = list1.minimumBy(Order.from((a1, a2) => a1.compareTo(a2))); + expect(ap.getOrElse(() => -1), 1); + }); + + test('drop', () { + final list1 = [1, 2, 3, 4, 5]; + final ap = list1.drop(2); + expect(eq(ap, [3, 4, 5]), true); + }); + + test('foldLeft', () { + final list1 = [1, 2, 3]; + final ap = list1.foldLeft(0, (b, t) => b - t); + expect(ap, -6); + }); + + test('foldLeftWithIndex', () { + final list1 = [1, 2, 3]; + final ap = list1.foldLeftWithIndex(0, (b, t, i) => b - t - i); + expect(ap, -9); + }); + + test('mapWithIndex', () { + final list1 = [1, 2, 3]; + final ap = list1.mapWithIndex((t, index) => '$t$index'); + expect(eq(ap, ['10', '21', '32']), true); + }); + + test('flatMap', () { + final list1 = [1, 2, 3]; + final ap = list1.flatMap((t) => [t, t + 1]); + expect(eq(ap, [1, 2, 2, 3, 3, 4]), true); + }); + + test('flatMapWithIndex', () { + final list1 = [1, 2, 3]; + final ap = list1.flatMapWithIndex((t, i) => [t, t + i]); + expect(eq(ap, [1, 1, 2, 3, 3, 5]), true); + }); + + test('ap', () { + final list1 = [1, 2, 3]; + final ap = list1.ap([(a) => a + 1, (a) => a + 2]); + expect(eq(ap, [2, 3, 3, 4, 4, 5]), true); + }); + + test('partition', () { + final list1 = [2, 4, 5, 6, 1, 3]; + final ap = list1.partition((t) => t > 2); + expect(ap.$1.length, 1); + expect(ap.$1.elementAt(0), 2); + + expect(ap.$2.length, 5); + expect(ap.$2.elementAt(0), 4); + expect(ap.$2.elementAt(1), 5); + expect(ap.$2.elementAt(2), 6); + expect(ap.$2.elementAt(3), 1); + expect(ap.$2.elementAt(4), 3); + }); + + group('all', () { + test('true', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.all((t) => t < 5); + expect(ap, true); + }); + + test('false', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.all((t) => t < 4); + expect(ap, false); + }); + }); + + group('elem', () { + test('true', () { + final list1 = [1, 2, 3, 4]; + final ap1 = list1.elem(1); + final ap2 = list1.elem(2); + final ap3 = list1.elem(3); + final ap4 = list1.elem(4); + expect(ap1, true); + expect(ap2, true); + expect(ap3, true); + expect(ap4, true); + }); + + test('false', () { + final list1 = [1, 2, 3, 4]; + final ap1 = list1.elem(-1); + final ap2 = list1.elem(0); + final ap3 = list1.elem(5); + final ap4 = list1.elem(6); + expect(ap1, false); + expect(ap2, false); + expect(ap3, false); + expect(ap4, false); + }); + }); + + group('notElem', () { + test('false', () { + final list1 = [1, 2, 3, 4]; + final ap1 = list1.notElem(1); + final ap2 = list1.notElem(2); + final ap3 = list1.notElem(3); + final ap4 = list1.notElem(4); + expect(ap1, false); + expect(ap2, false); + expect(ap3, false); + expect(ap4, false); + }); + + test('true', () { + final list1 = [1, 2, 3, 4]; + final ap1 = list1.notElem(-1); + final ap2 = list1.notElem(0); + final ap3 = list1.notElem(5); + final ap4 = list1.notElem(6); + expect(ap1, true); + expect(ap2, true); + expect(ap3, true); + expect(ap4, true); + }); + }); + }); + + group('FpdartOnMutableIterableOfIterable', () { + test('concat', () { + final list1 = [ + [1, 2], + [2, 3], + [3, 4] + ]; + final ap = list1.flatten; + + expect(eq(ap, [1, 2, 2, 3, 3, 4]), true); + }); + }); + + group('FpdartTraversableIterable', () { + group('traverseOption', () { + test('Some', () { + final list = [1, 2, 3, 4]; + final result = list.traverseOption(some); + result.matchTestSome((t) { + expect(list, t); + }); + }); + + test('None', () { + final list = [1, 2, 3, 4]; + final result = + list.traverseOption((t) => t == 3 ? none() : some(t)); + expect(result, isA()); + }); + }); + + group('traverseOptionWithIndex', () { + test('Some', () { + final list = [1, 2, 3, 4]; + final result = list.traverseOptionWithIndex((a, i) => some(a + i)); + result.matchTestSome((t) { + expect(t, [1, 3, 5, 7]); + }); + }); + + test('None', () { + final list = [1, 2, 3, 4]; + final result = list.traverseOptionWithIndex( + (a, i) => i == 3 ? none() : some(a + i)); + expect(result, isA()); + }); + }); + + group('traverseEither', () { + test('Right', () { + final list = [1, 2, 3, 4]; + final result = list.traverseEither(right); + result.matchTestRight((t) { + expect(list, t); + }); + }); + + test('Left', () { + final list = [1, 2, 3, 4]; + final result = + list.traverseEither((t) => t == 3 ? left("Error") : right(t)); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + }); + }); + + group('traverseEitherWithIndex', () { + test('Right', () { + final list = [1, 2, 3, 4]; + final result = list.traverseEitherWithIndex((a, i) => right(a + i)); + result.matchTestRight((t) { + expect(t, [1, 3, 5, 7]); + }); + }); + + test('Left', () { + final list = [1, 2, 3, 4]; + final result = list.traverseEitherWithIndex( + (a, i) => i == 3 ? left("Error") : right(a + i)); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + }); + }); + + group('traverseIOEither', () { + test('Right', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIOEither((a) { + sideEffect += 1; + return IOEither.of("$a"); + }); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestRight((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, list.length); + }); + + test('Left', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIOEither((a) { + sideEffect += 1; + return a % 2 == 0 ? IOEither.left("Error") : IOEither.of("$a"); + }); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + group('traverseIOEitherWithIndex', () { + test('Right', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIOEitherWithIndex((a, i) { + sideEffect += 1; + return IOEither.of("$a$i"); + }); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestRight((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, list.length); + }); + + test('Left', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIOEitherWithIndex((a, i) { + sideEffect += 1; + return a % 2 == 0 ? IOEither.left("Error") : IOEither.of("$a$i"); + }); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + test('traverseIO', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIO((a) { + sideEffect += 1; + return IO.of("$a"); + }); + expect(sideEffect, 0); + final result = traverse.run(); + expect(result, ['1', '2', '3', '4', '5', '6']); + expect(sideEffect, list.length); + }); + + test('traverseIOWithIndex', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIOWithIndex((a, i) { + sideEffect += 1; + return IO.of("$a$i"); + }); + expect(sideEffect, 0); + final result = traverse.run(); + expect(result, ['10', '21', '32', '43', '54', '65']); + expect(sideEffect, list.length); + }); + + test('traverseTask', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTask( + (a) => Task( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return "$a"; + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, ['1', '2', '3', '4', '5', '6']); + expect(sideEffect, list.length); + }); + + test('traverseTaskWithIndex', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskWithIndex( + (a, i) => Task( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return "$a$i"; + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, ['10', '21', '32', '43', '54', '65']); + expect(sideEffect, list.length); + }); + + test('traverseTaskSeq', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskSeq( + (a) => Task( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a; + return "$a"; + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, ['1', '2', '3', '4', '5', '6']); + expect(sideEffect, 6); + }); + + test('traverseTaskWithIndexSeq', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskWithIndexSeq( + (a, i) => Task( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a + i; + return "$a$i"; + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, ['10', '21', '32', '43', '54', '65']); + expect(sideEffect, 11); + }); + + group('traverseIOOption', () { + test('Some', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIOOption( + (a) => IOOption( + () { + sideEffect += 1; + return some("$a"); + }, + ), + ); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestSome((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, list.length); + }); + + test('None', () { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseIOOption( + (a) => IOOption( + () { + sideEffect += 1; + return a % 2 == 0 ? some("$a") : none(); + }, + ), + ); + expect(sideEffect, 0); + final result = traverse.run(); + expect(result, isA()); + expect(sideEffect, list.length); + }); + }); + + group('traverseTaskOptionWithIndex', () { + test('Some', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionWithIndex( + (a, i) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return some("$a$i"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestSome((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, list.length); + }); + + test('None', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionWithIndex( + (a, i) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return a % 2 == 0 ? some("$a$i") : none(); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, isA()); + expect(sideEffect, list.length); + }); + }); + + group('traverseTaskOption', () { + test('Some', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOption( + (a) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return some("$a"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestSome((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, list.length); + }); + + test('None', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOption( + (a) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return a % 2 == 0 ? some("$a") : none(); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, isA()); + expect(sideEffect, list.length); + }); + }); + + group('traverseTaskOptionWithIndex', () { + test('Some', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionWithIndex( + (a, i) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return some("$a$i"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestSome((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, list.length); + }); + + test('None', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionWithIndex( + (a, i) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return a % 2 == 0 ? some("$a$i") : none(); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, isA()); + expect(sideEffect, list.length); + }); + }); + + group('traverseTaskOptionSeq', () { + test('Some', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionSeq( + (a) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a - 1; + return some("$a"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestSome((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, 5); + }); + + test('None', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionSeq( + (a) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a - 1; + return a % 2 == 0 ? some("$a") : none(); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, isA()); + expect(sideEffect, 5); + }); + }); + + group('traverseTaskOptionWithIndexSeq', () { + test('Some', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionWithIndexSeq( + (a, i) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a + i; + return some("$a$i"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestSome((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, 11); + }); + + test('None', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskOptionWithIndexSeq( + (a, i) => TaskOption( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a + i; + return a % 2 == 0 ? some("$a$i") : none(); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, isA()); + expect(sideEffect, 11); + }); + }); + + group('traverseTaskEither', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEither( + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right("$a"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, list.length); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEither( + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return a % 2 == 0 + ? right("$a") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + group('traverseTaskEitherWithIndex', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEitherWithIndex( + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right("$a$i"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, list.length); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEitherWithIndex( + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return a % 2 == 0 + ? right("$a$i") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + group('traverseTaskEitherSeq', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEitherSeq( + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a - 1; + return right("$a"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, 5); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEitherSeq( + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a - 1; + return a % 2 == 0 + ? right("$a") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, 5); + }); + }); + + group('traverseTaskEitherWithIndexSeq', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEitherWithIndexSeq( + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a + i; + return right("$a$i"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, 11); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = list.traverseTaskEitherWithIndexSeq( + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a + i; + return a % 2 == 0 + ? right("$a$i") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, 11); + }); + }); + }); + + group('FpdartSequenceIterableOption', () { + group('sequenceOption', () { + test('Some', () { + final list = [some(1), some(2), some(3), some(4)]; + final result = list.sequenceOption(); + result.matchTestSome((t) { + expect(t, [1, 2, 3, 4]); + }); + }); + + test('None', () { + final list = [some(1), none(), some(3), some(4)]; + final result = list.sequenceOption(); + expect(result, isA()); + }); + }); + }); + + group('FpdartSequenceIterableIO', () { + test('sequenceIO', () { + var sideEffect = 0; + final list = [ + IO(() { + sideEffect += 1; + return 1; + }), + IO(() { + sideEffect += 1; + return 2; + }), + IO(() { + sideEffect += 1; + return 3; + }), + IO(() { + sideEffect += 1; + return 4; + }) + ]; + final traverse = list.sequenceIO(); + expect(sideEffect, 0); + final result = traverse.run(); + expect(result, [1, 2, 3, 4]); + expect(sideEffect, list.length); + }); + }); + + group('FpdartSequenceIterableTask', () { + test('sequenceTask', () async { + var sideEffect = 0; + final list = [ + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return 1; + }), + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return 2; + }), + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return 3; + }), + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return 4; + }), + ]; + final traverse = list.sequenceTask(); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, [1, 2, 3, 4]); + expect(sideEffect, list.length); + }); + + test('sequenceTaskSeq', () async { + var sideEffect = 0; + final list = [ + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect = 0; + return 1; + }), + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect = 1; + return 2; + }), + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect = 2; + return 3; + }), + Task(() async { + await AsyncUtils.waitFuture(); + sideEffect = 3; + return 4; + }), + ]; + final traverse = list.sequenceTaskSeq(); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, [1, 2, 3, 4]); + expect(sideEffect, 3); + }); + }); + + group('FpdartSequenceIterableEither', () { + group('sequenceEither', () { + test('Right', () { + final list = [right(1), right(2), right(3), right(4)]; + final result = list.sequenceEither(); + result.matchTestRight((r) { + expect(r, [1, 2, 3, 4]); + }); + }); + + test('Left', () { + final list = [ + right(1), + left("Error"), + right(3), + right(4) + ]; + final result = list.sequenceEither(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + }); + }); + + test('rightsEither', () { + final list = [ + right(1), + right(2), + left('a'), + left('b'), + right(3), + ]; + final result = list.rightsEither(); + expect(result, [1, 2, 3]); + }); + + test('leftsEither', () { + final list = [ + right(1), + right(2), + left('a'), + left('b'), + right(3), + ]; + final result = list.leftsEither(); + expect(result, ['a', 'b']); + }); + + test('partitionEithersEither', () { + final list = [ + right(1), + right(2), + left('a'), + left('b'), + right(3), + ]; + final result = list.partitionEithersEither(); + expect(result.$1, ['a', 'b']); + expect(result.$2, [1, 2, 3]); + }); + }); + + group('FpdartSequenceIterableIOOption', () { + group('sequenceIOOption', () { + test('Some', () { + var sideEffect = 0; + final list = [ + IOOption(() { + sideEffect += 1; + return some(1); + }), + IOOption(() { + sideEffect += 1; + return some(2); + }), + IOOption(() { + sideEffect += 1; + return some(3); + }), + IOOption(() { + sideEffect += 1; + return some(4); + }), + ]; + final traverse = list.sequenceIOOption(); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestSome((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, list.length); + }); + + test('None', () { + var sideEffect = 0; + final list = [ + IOOption(() { + sideEffect += 1; + return some(1); + }), + IOOption(() { + sideEffect += 1; + return none(); + }), + IOOption(() { + sideEffect += 1; + return some(3); + }), + IOOption(() { + sideEffect += 1; + return some(4); + }), + ]; + final traverse = list.sequenceIOOption(); + expect(sideEffect, 0); + final result = traverse.run(); + expect(result, isA()); + expect(sideEffect, list.length); + }); + }); + }); + + group('FpdartSequenceIterableTaskOption', () { + group('sequenceTaskOption', () { + test('Some', () async { + var sideEffect = 0; + final list = [ + TaskOption(() async { + sideEffect += 1; + return some(1); + }), + TaskOption(() async { + sideEffect += 1; + return some(2); + }), + TaskOption(() async { + sideEffect += 1; + return some(3); + }), + TaskOption(() async { + sideEffect += 1; + return some(4); + }), + ]; + final traverse = list.sequenceTaskOption(); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestSome((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, list.length); + }); + + test('None', () async { + var sideEffect = 0; + final list = [ + TaskOption(() async { + sideEffect += 1; + return some(1); + }), + TaskOption(() async { + sideEffect += 1; + return none(); + }), + TaskOption(() async { + sideEffect += 1; + return some(3); + }), + TaskOption(() async { + sideEffect += 1; + return some(4); + }), + ]; + final traverse = list.sequenceTaskOption(); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, isA()); + expect(sideEffect, list.length); + }); + }); + + group('sequenceTaskOptionSeq', () { + test('Some', () async { + var sideEffect = 0; + final list = [ + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 0; + return some(1); + }), + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 1; + return some(2); + }), + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 2; + return some(3); + }), + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 3; + return some(4); + }), + ]; + final traverse = list.sequenceTaskOptionSeq(); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestSome((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, 3); + }); + + test('None', () async { + var sideEffect = 0; + final list = [ + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 0; + return some(1); + }), + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 1; + return none(); + }), + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 2; + return some(3); + }), + TaskOption(() async { + await AsyncUtils.waitFuture(); + sideEffect = 3; + return some(4); + }), + ]; + final traverse = list.sequenceTaskOptionSeq(); + expect(sideEffect, 0); + final result = await traverse.run(); + expect(result, isA()); + expect(sideEffect, 3); + }); + }); + }); + + group('FpdartSequenceIterableTaskEither', () { + group('sequenceTaskEither', () { + test('Right', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + sideEffect += 1; + return right(1); + }), + TaskEither(() async { + sideEffect += 1; + return right(2); + }), + TaskEither(() async { + sideEffect += 1; + return right(3); + }), + TaskEither(() async { + sideEffect += 1; + return right(4); + }), + ]; + final traverse = list.sequenceTaskEither(); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, list.length); + }); + + test('Left', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + sideEffect += 1; + return right(1); + }), + TaskEither(() async { + sideEffect += 1; + return left("Error"); + }), + TaskEither(() async { + sideEffect += 1; + return right(3); + }), + TaskEither(() async { + sideEffect += 1; + return right(4); + }), + ]; + final traverse = list.sequenceTaskEither(); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + group('sequenceTaskEitherSeq', () { + test('Right', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 0; + return right(1); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 1; + return right(2); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 2; + return right(3); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 3; + return right(4); + }), + ]; + final traverse = list.sequenceTaskEitherSeq(); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, 3); + }); + + test('Left', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 0; + return right(1); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 1; + return left("Error"); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 2; + return right(3); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 3; + return right(4); + }), + ]; + final traverse = list.sequenceTaskEitherSeq(); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, 3); + }); + }); + }); + + group('FpdartSequenceIterableIOEither', () { + group('sequenceIOEither', () { + test('Right', () { + var sideEffect = 0; + final list = [ + IOEither(() { + sideEffect += 1; + return right(1); + }), + IOEither(() { + sideEffect += 1; + return right(2); + }), + IOEither(() { + sideEffect += 1; + return right(3); + }), + IOEither(() { + sideEffect += 1; + return right(4); + }), + ]; + final traverse = list.sequenceIOEither(); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestRight((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, list.length); + }); + + test('Left', () { + var sideEffect = 0; + final list = [ + IOEither(() { + sideEffect += 1; + return right(1); + }), + IOEither(() { + sideEffect += 1; + return left("Error"); + }), + IOEither(() { + sideEffect += 1; + return right(3); + }), + IOEither(() { + sideEffect += 1; + return right(4); + }), + ]; + final traverse = list.sequenceIOEither(); + expect(sideEffect, 0); + final result = traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + }); +} diff --git a/packages/fpdart/test/src/extension/list_extension_test.dart b/packages/fpdart/test/src/extension/list_extension_test.dart index ea526ba6..8e3139ae 100644 --- a/packages/fpdart/test/src/extension/list_extension_test.dart +++ b/packages/fpdart/test/src/extension/list_extension_test.dart @@ -2,1620 +2,34 @@ import 'package:fpdart/fpdart.dart'; import '../utils/utils.dart'; -/// Used to test sorting with [DateTime] (`sortWithDate`) -class SortDate { - final int id; - final DateTime date; - const SortDate(this.id, this.date); -} - void main() { - /// Check if two [Iterable] have the same element in the same order - bool eq(Iterable a, Iterable b) => a.foldLeftWithIndex( - false, - (a, e, i) => e == b.elementAt(i), - ); - - group('FpdartOnMutableIterable', () { - test('zipWith', () { - final list1 = [1, 2]; - final list2 = ['a', 'b']; - final ap = - list1.zipWith((t) => (i) => (t + i.length) / 2); - final result = ap(list2); - - expect(eq(result, [1.0, 1.5]), true); - }); - - test('zip', () { - final list1 = [1, 2]; - final list2 = ['a', 'b']; - final ap = list1.zip(list2); - - expect(eq(ap, [(1, 'a'), (2, 'b')]), true); - }); - - test('filter', () { - final list1 = [1, 2, 3, 4, 5, 6]; - final ap = list1.filter((t) => t > 3); - - expect(eq(ap, [4, 5, 6]), true); - }); - - test('plus', () { - final list1 = [1, 2, 3, 4, 5, 6]; - final ap = list1.plus([7, 8]); - - expect(eq(ap, [1, 2, 3, 4, 5, 6, 7, 8]), true); - }); - - test('append', () { - final list1 = [1, 2, 3, 4, 5, 6]; - final ap = list1.append(7); - - expect(eq(ap, [1, 2, 3, 4, 5, 6, 7]), true); - }); - - test('prepend', () { - final list1 = [1, 2, 3, 4, 5, 6]; - final ap = list1.prepend(0); - - expect(eq(ap, [0, 1, 2, 3, 4, 5, 6]), true); - }); - - test('insertBy', () { - final list1 = [1, 2, 3, 4, 5, 6]; - final ap = list1.insertBy(Order.from((a1, a2) => a1.compareTo(a2)), 4); - - expect(eq(ap, [1, 2, 3, 4, 4, 5, 6]), true); - }); - - test('insertWith', () { - final list1 = [ - SortDate(2, DateTime(2019)), - SortDate(4, DateTime(2017)), - SortDate(1, DateTime(2020)), - SortDate(3, DateTime(2018)), - ]; - final ap = list1.insertWith( - (instance) => instance.date, - dateOrder, - SortDate(5, DateTime(2021)), - ); - - expect(ap.elementAt(4).id, 5); - expect(ap.elementAt(4).date.year, 2021); - }); - - test('sortBy', () { - final list1 = [2, 6, 4, 1, 5, 3]; - final ap = list1.sortBy(Order.from((a1, a2) => a1.compareTo(a2))); - - expect(eq(ap, [1, 2, 3, 4, 5, 6]), true); - }); - - test('sortWith', () { - final list1 = [ - SortDate(2, DateTime(2019)), - SortDate(4, DateTime(2017)), - SortDate(1, DateTime(2020)), - SortDate(3, DateTime(2018)), - ]; - final ap = list1.sortWith((instance) => instance.date, dateOrder); - - expect(ap.elementAt(0).id, 4); - expect(ap.elementAt(1).id, 3); - expect(ap.elementAt(2).id, 2); - expect(ap.elementAt(3).id, 1); - }); - - test('sortWithDate', () { - final list1 = [ - SortDate(2, DateTime(2019)), - SortDate(4, DateTime(2017)), - SortDate(1, DateTime(2020)), - SortDate(3, DateTime(2018)), - ]; - final ap = list1.sortWithDate((instance) => instance.date); - - expect(ap.elementAt(0).date.year, 2017); - expect(ap.elementAt(1).date.year, 2018); - expect(ap.elementAt(2).date.year, 2019); - expect(ap.elementAt(3).date.year, 2020); - }); - - test('sortBy', () { - final list1 = [2, 6, 4, 1, 5, 3]; - final ap = list1.sortBy(Order.from((a1, a2) => a1.compareTo(a2))); - - expect(eq(ap, [1, 2, 3, 4, 5, 6]), true); - }); - - test('intersect', () { - final list1 = [1, 2, 3, 4, 5, 6]; - final ap = list1.intersect([1, 2, 3, 10, 11, 12]); - - expect(eq(ap, [1, 2, 3]), true); - }); - - group('head', () { - test('Some', () { - final list1 = [1, 2]; - final ap = list1.head; - expect(ap, isA()); - expect(ap.getOrElse(() => -1), 1); - }); - - test('None', () { - final List list1 = []; - final ap = list1.head; - expect(ap, isA()); - expect(ap.getOrElse(() => -1), -1); - }); - }); - - group('firstOption', () { - test('Some', () { - final list1 = [1, 2]; - final ap = list1.firstOption; - expect(ap, isA()); - expect(ap.getOrElse(() => -1), 1); - }); - }); - - group('tail', () { - test('Some', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.tail; - expect(ap, isA()); - expect(ap.getOrElse(() => []), [2, 3, 4]); - }); - }); - - group('init', () { - test('Some', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.init; - expect(ap, isA()); - expect(ap.getOrElse(() => []), [1, 2, 3]); - }); - }); - - group('lastOption', () { - test('Some', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.lastOption; - expect(ap, isA()); - expect(ap.getOrElse(() => -1), 4); - }); - }); - - test('takeWhileLeft', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.takeWhileLeft((t) => t < 3); - expect(eq(ap, [1, 2]), true); - }); - - test('dropWhileLeft', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.dropWhileLeft((t) => t < 3); - expect(eq(ap, [3, 4]), true); - }); - - test('takeWhileRight', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.takeWhileRight((t) => t > 2); - expect(eq(ap, [3, 4]), true); - }); - - test('dropWhileRight', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.dropWhileRight((t) => t > 2); - expect(eq(ap, [1, 2]), true); - }); - - test('span', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.span((t) => t < 3); - expect(eq(ap.$1, [1, 2]), true); - expect(eq(ap.$2, [3, 4]), true); - }); - - test('breakI', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.breakI((t) => t > 2); - expect(eq(ap.$1, [1, 2]), true); - expect(eq(ap.$2, [3, 4]), true); - }); - - test('splitAt', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.splitAt(2); - expect(eq(ap.$1, [1, 2]), true); - expect(eq(ap.$2, [3, 4]), true); - }); - - test('delete', () { - final list1 = [1, 2, 3, 2, 4, 2]; - final ap = list1.delete(2); - expect(eq(ap, [1, 3, 2, 4, 2]), true); - }); - - test('maximumBy', () { - final list1 = [2, 5, 4, 6, 1, 3]; - final ap = list1.maximumBy(Order.from((a1, a2) => a1.compareTo(a2))); - expect(ap.getOrElse(() => -1), 6); - }); - - test('minimumBy', () { - final list1 = [2, 5, 4, 6, 1, 3]; - final ap = list1.minimumBy(Order.from((a1, a2) => a1.compareTo(a2))); - expect(ap.getOrElse(() => -1), 1); - }); - - test('drop', () { - final list1 = [1, 2, 3, 4, 5]; - final ap = list1.drop(2); - expect(eq(ap, [3, 4, 5]), true); - }); - - test('foldLeft', () { - final list1 = [1, 2, 3]; - final ap = list1.foldLeft(0, (b, t) => b - t); - expect(ap, -6); - }); - - test('foldLeftWithIndex', () { - final list1 = [1, 2, 3]; - final ap = list1.foldLeftWithIndex(0, (b, t, i) => b - t - i); - expect(ap, -9); - }); - + group('FpdartOnList', () { test('foldRight', () { final list1 = [1, 2, 3]; - final ap = list1.foldRight(0, (b, t) => b - t); + final ap = list1.foldRight(0.0, (t, b) => b - t); expect(ap, 2); }); test('foldRightWithIndex', () { final list1 = [1, 2, 3]; - final ap = list1.foldRightWithIndex(0, (b, t, i) => b - t - i); + final ap = list1.foldRightWithIndex(0.0, (t, b, i) => b - t - i); expect(ap, 1); }); - test('mapWithIndex', () { - final list1 = [1, 2, 3]; - final ap = list1.mapWithIndex((t, index) => '$t$index'); - expect(eq(ap, ['10', '21', '32']), true); - }); - - test('concatMap', () { - final list1 = [1, 2, 3]; - final ap = list1.concatMap((t) => [t, t + 1]); - expect(eq(ap, [1, 2, 2, 3, 3, 4]), true); - }); - - test('concatMapWithIndex', () { - final list1 = [1, 2, 3]; - final ap = list1.concatMapWithIndex((t, i) => [t, t + i]); - expect(eq(ap, [1, 1, 2, 3, 3, 5]), true); - }); - - test('flatMap', () { - final list1 = [1, 2, 3]; - final ap = list1.flatMap((t) => [t, t + 1]); - expect(eq(ap, [1, 2, 2, 3, 3, 4]), true); - }); - - test('flatMapWithIndex', () { - final list1 = [1, 2, 3]; - final ap = list1.flatMapWithIndex((t, i) => [t, t + i]); - expect(eq(ap, [1, 1, 2, 3, 3, 5]), true); - }); - - test('bind', () { - final list1 = [1, 2, 3]; - final ap = list1.bind((t) => [t, t + 1]); - expect(eq(ap, [1, 2, 2, 3, 3, 4]), true); - }); - - test('bindWithIndex', () { - final list1 = [1, 2, 3]; - final ap = list1.bindWithIndex((t, i) => [t, t + i]); - expect(eq(ap, [1, 1, 2, 3, 3, 5]), true); - }); - - test('ap', () { - final list1 = [1, 2, 3]; - final ap = list1.ap([(a) => a + 1, (a) => a + 2]); - expect(eq(ap, [2, 3, 3, 4, 4, 5]), true); - }); - - test('partition', () { - final list1 = [2, 4, 5, 6, 1, 3]; - final ap = list1.partition((t) => t > 2); - expect(eq(ap.$1, [2, 1]), true); - expect(eq(ap.$2, [4, 5, 6, 3]), true); - }); - - group('all', () { - test('true', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.all((t) => t < 5); - expect(ap, true); - }); - - test('false', () { - final list1 = [1, 2, 3, 4]; - final ap = list1.all((t) => t < 4); - expect(ap, false); - }); - }); - - group('elem', () { - test('true', () { - final list1 = [1, 2, 3, 4]; - final ap1 = list1.elem(1); - final ap2 = list1.elem(2); - final ap3 = list1.elem(3); - final ap4 = list1.elem(4); - expect(ap1, true); - expect(ap2, true); - expect(ap3, true); - expect(ap4, true); - }); - - test('false', () { - final list1 = [1, 2, 3, 4]; - final ap1 = list1.elem(-1); - final ap2 = list1.elem(0); - final ap3 = list1.elem(5); - final ap4 = list1.elem(6); - expect(ap1, false); - expect(ap2, false); - expect(ap3, false); - expect(ap4, false); - }); - }); - - group('notElem', () { - test('false', () { - final list1 = [1, 2, 3, 4]; - final ap1 = list1.notElem(1); - final ap2 = list1.notElem(2); - final ap3 = list1.notElem(3); - final ap4 = list1.notElem(4); - expect(ap1, false); - expect(ap2, false); - expect(ap3, false); - expect(ap4, false); - }); - - test('true', () { - final list1 = [1, 2, 3, 4]; - final ap1 = list1.notElem(-1); - final ap2 = list1.notElem(0); - final ap3 = list1.notElem(5); - final ap4 = list1.notElem(6); - expect(ap1, true); - expect(ap2, true); - expect(ap3, true); - expect(ap4, true); - }); - }); - }); - - group('FpdartOnMutableIterableOfIterable', () { - test('concat', () { - final list1 = [ - [1, 2], - [2, 3], - [3, 4] - ]; - final ap = list1.concat; - - expect(eq(ap, [1, 2, 2, 3, 3, 4]), true); - }); - }); - - group('FpdartTraversableIterable', () { - group('traverseOption', () { - test('Some', () { - final list = [1, 2, 3, 4]; - final result = list.traverseOption(some); - result.matchTestSome((t) { - expect(list, t); - }); - }); - - test('None', () { - final list = [1, 2, 3, 4]; - final result = - list.traverseOption((t) => t == 3 ? none() : some(t)); - expect(result, isA()); - }); - }); - - group('traverseOptionWithIndex', () { - test('Some', () { - final list = [1, 2, 3, 4]; - final result = list.traverseOptionWithIndex((a, i) => some(a + i)); - result.matchTestSome((t) { - expect(t, [1, 3, 5, 7]); - }); - }); - - test('None', () { - final list = [1, 2, 3, 4]; - final result = list.traverseOptionWithIndex( - (a, i) => i == 3 ? none() : some(a + i)); - expect(result, isA()); - }); - }); - - group('traverseEither', () { - test('Right', () { - final list = [1, 2, 3, 4]; - final result = list.traverseEither(right); - result.matchTestRight((t) { - expect(list, t); - }); - }); - - test('Left', () { - final list = [1, 2, 3, 4]; - final result = - list.traverseEither((t) => t == 3 ? left("Error") : right(t)); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - - group('traverseEitherWithIndex', () { - test('Right', () { - final list = [1, 2, 3, 4]; - final result = list.traverseEitherWithIndex((a, i) => right(a + i)); - result.matchTestRight((t) { - expect(t, [1, 3, 5, 7]); - }); - }); - - test('Left', () { - final list = [1, 2, 3, 4]; - final result = list.traverseEitherWithIndex( - (a, i) => i == 3 ? left("Error") : right(a + i)); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - - group('traverseIOEither', () { - test('Right', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIOEither((a) { - sideEffect += 1; - return IOEither.of("$a"); - }); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestRight((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); - }); - expect(sideEffect, list.length); - }); - - test('Left', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIOEither((a) { - sideEffect += 1; - return a % 2 == 0 ? IOEither.left("Error") : IOEither.of("$a"); - }); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, list.length); - }); - }); - - group('traverseIOEitherWithIndex', () { - test('Right', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIOEitherWithIndex((a, i) { - sideEffect += 1; - return IOEither.of("$a$i"); - }); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestRight((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); - }); - expect(sideEffect, list.length); - }); - - test('Left', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIOEitherWithIndex((a, i) { - sideEffect += 1; - return a % 2 == 0 ? IOEither.left("Error") : IOEither.of("$a$i"); - }); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, list.length); - }); - }); - - test('traverseIO', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIO((a) { - sideEffect += 1; - return IO.of("$a"); - }); - expect(sideEffect, 0); - final result = traverse.run(); - expect(result, ['1', '2', '3', '4', '5', '6']); - expect(sideEffect, list.length); - }); - - test('traverseIOWithIndex', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIOWithIndex((a, i) { - sideEffect += 1; - return IO.of("$a$i"); - }); - expect(sideEffect, 0); - final result = traverse.run(); - expect(result, ['10', '21', '32', '43', '54', '65']); - expect(sideEffect, list.length); - }); - - test('traverseTask', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTask( - (a) => Task( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return "$a"; - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, ['1', '2', '3', '4', '5', '6']); - expect(sideEffect, list.length); - }); - - test('traverseTaskWithIndex', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskWithIndex( - (a, i) => Task( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return "$a$i"; - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, ['10', '21', '32', '43', '54', '65']); - expect(sideEffect, list.length); - }); - - test('traverseTaskSeq', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskSeq( - (a) => Task( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a; - return "$a"; - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, ['1', '2', '3', '4', '5', '6']); - expect(sideEffect, 6); - }); - - test('traverseTaskWithIndexSeq', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskWithIndexSeq( - (a, i) => Task( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a + i; - return "$a$i"; - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, ['10', '21', '32', '43', '54', '65']); - expect(sideEffect, 11); - }); - - group('traverseIOOption', () { - test('Some', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIOOption( - (a) => IOOption( - () { - sideEffect += 1; - return some("$a"); - }, - ), - ); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestSome((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); - }); - expect(sideEffect, list.length); - }); - - test('None', () { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseIOOption( - (a) => IOOption( - () { - sideEffect += 1; - return a % 2 == 0 ? some("$a") : none(); - }, - ), - ); - expect(sideEffect, 0); - final result = traverse.run(); - expect(result, isA()); - expect(sideEffect, list.length); - }); - }); - - group('traverseTaskOptionWithIndex', () { - test('Some', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionWithIndex( - (a, i) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return some("$a$i"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestSome((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); - }); - expect(sideEffect, list.length); - }); - - test('None', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionWithIndex( - (a, i) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return a % 2 == 0 ? some("$a$i") : none(); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, isA()); - expect(sideEffect, list.length); - }); - }); - - group('traverseTaskOption', () { - test('Some', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOption( - (a) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return some("$a"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestSome((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); - }); - expect(sideEffect, list.length); - }); - - test('None', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOption( - (a) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return a % 2 == 0 ? some("$a") : none(); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, isA()); - expect(sideEffect, list.length); - }); - }); - - group('traverseTaskOptionWithIndex', () { - test('Some', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionWithIndex( - (a, i) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return some("$a$i"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestSome((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); - }); - expect(sideEffect, list.length); - }); - - test('None', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionWithIndex( - (a, i) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return a % 2 == 0 ? some("$a$i") : none(); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, isA()); - expect(sideEffect, list.length); - }); - }); - - group('traverseTaskOptionSeq', () { - test('Some', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionSeq( - (a) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a - 1; - return some("$a"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestSome((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); - }); - expect(sideEffect, 5); - }); - - test('None', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionSeq( - (a) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a - 1; - return a % 2 == 0 ? some("$a") : none(); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, isA()); - expect(sideEffect, 5); - }); - }); - - group('traverseTaskOptionWithIndexSeq', () { - test('Some', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionWithIndexSeq( - (a, i) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a + i; - return some("$a$i"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestSome((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); - }); - expect(sideEffect, 11); - }); - - test('None', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskOptionWithIndexSeq( - (a, i) => TaskOption( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a + i; - return a % 2 == 0 ? some("$a$i") : none(); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, isA()); - expect(sideEffect, 11); - }); - }); - - group('traverseTaskEither', () { - test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEither( - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right("$a"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); - }); - expect(sideEffect, list.length); - }); - - test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEither( - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return a % 2 == 0 - ? right("$a") - : left("Error"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, list.length); - }); - }); - - group('traverseTaskEitherWithIndex', () { - test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEitherWithIndex( - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right("$a$i"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); - }); - expect(sideEffect, list.length); - }); - - test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEitherWithIndex( - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return a % 2 == 0 - ? right("$a$i") - : left("Error"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, list.length); - }); - }); - - group('traverseTaskEitherSeq', () { - test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEitherSeq( - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a - 1; - return right("$a"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); - }); - expect(sideEffect, 5); - }); - - test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEitherSeq( - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a - 1; - return a % 2 == 0 - ? right("$a") - : left("Error"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, 5); - }); - }); - - group('traverseTaskEitherWithIndexSeq', () { - test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEitherWithIndexSeq( - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a + i; - return right("$a$i"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); - }); - expect(sideEffect, 11); - }); - - test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = list.traverseTaskEitherWithIndexSeq( - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a + i; - return a % 2 == 0 - ? right("$a$i") - : left("Error"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, 11); - }); - }); - }); - - group('FpdartSequenceIterableOption', () { - group('sequenceOption', () { - test('Some', () { - final list = [some(1), some(2), some(3), some(4)]; - final result = list.sequenceOption(); - result.matchTestSome((t) { - expect(t, [1, 2, 3, 4]); - }); - }); - - test('None', () { - final list = [some(1), none(), some(3), some(4)]; - final result = list.sequenceOption(); - expect(result, isA()); - }); - }); - }); - - group('FpdartSequenceIterableIO', () { - test('sequenceIO', () { - var sideEffect = 0; - final list = [ - IO(() { - sideEffect += 1; - return 1; - }), - IO(() { - sideEffect += 1; - return 2; - }), - IO(() { - sideEffect += 1; - return 3; - }), - IO(() { - sideEffect += 1; - return 4; - }) - ]; - final traverse = list.sequenceIO(); - expect(sideEffect, 0); - final result = traverse.run(); - expect(result, [1, 2, 3, 4]); - expect(sideEffect, list.length); - }); - }); - - group('FpdartSequenceIterableTask', () { - test('sequenceTask', () async { - var sideEffect = 0; - final list = [ - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return 1; - }), - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return 2; - }), - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return 3; - }), - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return 4; - }), - ]; - final traverse = list.sequenceTask(); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, [1, 2, 3, 4]); - expect(sideEffect, list.length); - }); - - test('sequenceTaskSeq', () async { - var sideEffect = 0; - final list = [ - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect = 0; - return 1; - }), - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect = 1; - return 2; - }), - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect = 2; - return 3; - }), - Task(() async { - await AsyncUtils.waitFuture(); - sideEffect = 3; - return 4; - }), - ]; - final traverse = list.sequenceTaskSeq(); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, [1, 2, 3, 4]); - expect(sideEffect, 3); - }); - }); - - group('FpdartSequenceIterableEither', () { - group('sequenceEither', () { - test('Right', () { - final list = [right(1), right(2), right(3), right(4)]; - final result = list.sequenceEither(); - result.matchTestRight((r) { - expect(r, [1, 2, 3, 4]); - }); - }); - - test('Left', () { - final list = [ - right(1), - left("Error"), - right(3), - right(4) - ]; - final result = list.sequenceEither(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - - test('rightsEither', () { - final list = [ - right(1), - right(2), - left('a'), - left('b'), - right(3), - ]; - final result = list.rightsEither(); - expect(result, [1, 2, 3]); - }); - - test('leftsEither', () { - final list = [ - right(1), - right(2), - left('a'), - left('b'), - right(3), - ]; - final result = list.leftsEither(); - expect(result, ['a', 'b']); - }); - - test('partitionEithersEither', () { - final list = [ - right(1), - right(2), - left('a'), - left('b'), - right(3), - ]; - final result = list.partitionEithersEither(); - expect(result.$1, ['a', 'b']); - expect(result.$2, [1, 2, 3]); - }); - }); - - group('FpdartSequenceIterableIOOption', () { - group('sequenceIOOption', () { - test('Some', () { - var sideEffect = 0; - final list = [ - IOOption(() { - sideEffect += 1; - return some(1); - }), - IOOption(() { - sideEffect += 1; - return some(2); - }), - IOOption(() { - sideEffect += 1; - return some(3); - }), - IOOption(() { - sideEffect += 1; - return some(4); - }), - ]; - final traverse = list.sequenceIOOption(); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestSome((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, list.length); - }); - - test('None', () { - var sideEffect = 0; - final list = [ - IOOption(() { - sideEffect += 1; - return some(1); - }), - IOOption(() { - sideEffect += 1; - return none(); - }), - IOOption(() { - sideEffect += 1; - return some(3); - }), - IOOption(() { - sideEffect += 1; - return some(4); - }), - ]; - final traverse = list.sequenceIOOption(); - expect(sideEffect, 0); - final result = traverse.run(); - expect(result, isA()); - expect(sideEffect, list.length); - }); - }); - }); - - group('FpdartSequenceIterableTaskOption', () { - group('sequenceTaskOption', () { - test('Some', () async { - var sideEffect = 0; - final list = [ - TaskOption(() async { - sideEffect += 1; - return some(1); - }), - TaskOption(() async { - sideEffect += 1; - return some(2); - }), - TaskOption(() async { - sideEffect += 1; - return some(3); - }), - TaskOption(() async { - sideEffect += 1; - return some(4); - }), - ]; - final traverse = list.sequenceTaskOption(); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestSome((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, list.length); - }); - - test('None', () async { - var sideEffect = 0; - final list = [ - TaskOption(() async { - sideEffect += 1; - return some(1); - }), - TaskOption(() async { - sideEffect += 1; - return none(); - }), - TaskOption(() async { - sideEffect += 1; - return some(3); - }), - TaskOption(() async { - sideEffect += 1; - return some(4); - }), - ]; - final traverse = list.sequenceTaskOption(); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, isA()); - expect(sideEffect, list.length); - }); - }); - - group('sequenceTaskOptionSeq', () { - test('Some', () async { - var sideEffect = 0; - final list = [ - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 0; - return some(1); - }), - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 1; - return some(2); - }), - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 2; - return some(3); - }), - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 3; - return some(4); - }), - ]; - final traverse = list.sequenceTaskOptionSeq(); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestSome((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, 3); - }); - - test('None', () async { - var sideEffect = 0; - final list = [ - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 0; - return some(1); - }), - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 1; - return none(); - }), - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 2; - return some(3); - }), - TaskOption(() async { - await AsyncUtils.waitFuture(); - sideEffect = 3; - return some(4); - }), - ]; - final traverse = list.sequenceTaskOptionSeq(); - expect(sideEffect, 0); - final result = await traverse.run(); - expect(result, isA()); - expect(sideEffect, 3); - }); - }); - }); - - group('FpdartSequenceIterableTaskEither', () { - group('sequenceTaskEither', () { - test('Right', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - sideEffect += 1; - return right(1); - }), - TaskEither(() async { - sideEffect += 1; - return right(2); - }), - TaskEither(() async { - sideEffect += 1; - return right(3); - }), - TaskEither(() async { - sideEffect += 1; - return right(4); - }), - ]; - final traverse = list.sequenceTaskEither(); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, list.length); - }); - - test('Left', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - sideEffect += 1; - return right(1); - }), - TaskEither(() async { - sideEffect += 1; - return left("Error"); - }), - TaskEither(() async { - sideEffect += 1; - return right(3); - }), - TaskEither(() async { - sideEffect += 1; - return right(4); - }), - ]; - final traverse = list.sequenceTaskEither(); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, list.length); - }); - }); - - group('sequenceTaskEitherSeq', () { - test('Right', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 0; - return right(1); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 1; - return right(2); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 2; - return right(3); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 3; - return right(4); - }), - ]; - final traverse = list.sequenceTaskEitherSeq(); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, 3); - }); - - test('Left', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 0; - return right(1); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 1; - return left("Error"); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 2; - return right(3); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 3; - return right(4); - }), - ]; - final traverse = list.sequenceTaskEitherSeq(); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, 3); - }); + test('takeWhileRight', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.takeWhileRight((t) => t > 2); + expect(ap.length, 2); + expect(ap.elementAt(0), 4); + expect(ap.elementAt(1), 3); }); - }); - - group('FpdartSequenceIterableIOEither', () { - group('sequenceIOEither', () { - test('Right', () { - var sideEffect = 0; - final list = [ - IOEither(() { - sideEffect += 1; - return right(1); - }), - IOEither(() { - sideEffect += 1; - return right(2); - }), - IOEither(() { - sideEffect += 1; - return right(3); - }), - IOEither(() { - sideEffect += 1; - return right(4); - }), - ]; - final traverse = list.sequenceIOEither(); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestRight((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, list.length); - }); - test('Left', () { - var sideEffect = 0; - final list = [ - IOEither(() { - sideEffect += 1; - return right(1); - }), - IOEither(() { - sideEffect += 1; - return left("Error"); - }), - IOEither(() { - sideEffect += 1; - return right(3); - }), - IOEither(() { - sideEffect += 1; - return right(4); - }), - ]; - final traverse = list.sequenceIOEither(); - expect(sideEffect, 0); - final result = traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, list.length); - }); + test('dropWhileRight', () { + final list1 = [1, 2, 3, 4]; + final ap = list1.dropWhileRight((t) => t > 2); + expect(ap.length, 2); + expect(ap.elementAt(0), 2); + expect(ap.elementAt(1), 1); }); }); } From b9a34dc5233c0b3e644104b8416af3b3c3b567b5 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 20 May 2023 19:40:41 +0200 Subject: [PATCH 35/71] added intersperse and prependAll --- packages/fpdart/CHANGELOG.md | 4 +- .../lib/src/extension/iterable_extension.dart | 46 +++++++------------ .../extension/iterable_extension_test.dart | 13 ++++++ 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 41739be7..87313102 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -22,8 +22,8 @@ - Improved performance - Correct return types (`Iterable` and `List`) ([#65](https://github.com/SandroMaglione/fpdart/issues/65)) - Added the following methods - - `intersectBy` (`Iterable`) - - `intersectWith` (`Iterable`) + - `prependAll` (`Iterable`) + - `intersperse` (`Iterable`) - Fixed the following methods ⚠️ - `takeWhileRight`: Result `List` reversed - `dropWhileRight`: Result `List` reversed diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index 49a6caee..7f3388c2 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -1,7 +1,6 @@ import '../date.dart'; import '../function.dart'; import '../option.dart'; -import '../typeclass/eq.dart'; import '../typeclass/order.dart'; import 'predicate_extension.dart'; @@ -90,6 +89,12 @@ extension FpdartOnIterable on Iterable { yield* this; } + /// Insert all the elements inside `other` at the beginning of the [Iterable]. + Iterable prependAll(Iterable other) sync* { + yield* other; + yield* this; + } + /// Insert `element` at the end of the [Iterable]. Iterable append(T element) sync* { yield* this; @@ -287,38 +292,19 @@ extension FpdartOnIterable on Iterable { } } - /// Return the intersection of two [Iterable] (all the elements that both [Iterable] have in common) - /// based on `eq` to check equality. - Iterable intersectBy(Eq eq, Iterable iterable) sync* { - for (var element in this) { + /// Return an [Iterable] placing an `middle` in between elements of the this [Iterable]. + Iterable intersperse(T middle) sync* { + if (isNotEmpty) { try { - final e = iterable.firstWhere( - (e) => eq.eqv(e, element), - ); - yield e; - } catch (_) { - continue; - } - } - } + // Check not last element + elementAt(1); - /// Return the intersection of two [Iterable] (all the elements that both [Iterable] have in common) - /// based on `eq` to check equality. - /// - /// `eq` refers to a value of type `A` extracted from `T` using `extract`. - Iterable intersectWith( - A Function(T t) extract, - Eq eq, - Iterable iterable, - ) sync* { - for (var element in this) { - try { - final e = iterable.firstWhere( - (e) => eq.eqv(extract(e), extract(element)), - ); - yield e; + yield first; + yield middle; + yield* skip(1).intersperse(middle); } catch (_) { - continue; + // No element at 1, this is the last of the list + yield first; } } } diff --git a/packages/fpdart/test/src/extension/iterable_extension_test.dart b/packages/fpdart/test/src/extension/iterable_extension_test.dart index 5a223348..8a682d98 100644 --- a/packages/fpdart/test/src/extension/iterable_extension_test.dart +++ b/packages/fpdart/test/src/extension/iterable_extension_test.dart @@ -64,6 +64,13 @@ void main() { expect(eq(ap, [0, 1, 2, 3, 4, 5, 6]), true); }); + test('prependAll', () { + final list1 = [1, 2, 3, 4, 5, 6]; + final ap = list1.prependAll([10, 11, 12]); + + expect(eq(ap, [10, 11, 12, 1, 2, 3, 4, 5, 6]), true); + }); + test('insertBy', () { final list1 = [1, 2, 3, 4, 5, 6]; final ap = list1.insertBy(Order.from((a1, a2) => a1.compareTo(a2)), 4); @@ -139,6 +146,12 @@ void main() { expect(eq(ap, [1, 2, 3]), true); }); + test('intersperse', () { + final ap = [1, 2, 3].intersperse(10); + + expect(eq(ap, [1, 10, 2, 10, 3]), true); + }); + group('head', () { test('Some', () { final list1 = [1, 2]; From c9f1d92725bd747415ea046785b174e89178ecd4 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sun, 21 May 2023 11:36:26 +0200 Subject: [PATCH 36/71] fix partition and add back breakI --- packages/fpdart/CHANGELOG.md | 2 -- .../lib/src/extension/iterable_extension.dart | 12 ++++++---- .../extension/iterable_extension_test.dart | 23 +++++++++++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 87313102..bc837b9a 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -27,7 +27,6 @@ - Fixed the following methods ⚠️ - `takeWhileRight`: Result `List` reversed - `dropWhileRight`: Result `List` reversed - - `partition`, `span`: Returns longest **prefix** (and not all elements matching `test`) - Updated the following methods ⚠️ - `foldRight`, `foldRightWithIndex` (`List`): Changed parameter order in `combine` function - `zipWith` (`Iterable`): Changed parameters definition, no more curried @@ -35,7 +34,6 @@ - `plus` → `concat` (`Iterable`) - `concat` → `flatten` (on `Iterable>`) - Removed the following methods ⚠️ - - `breakI` (use `partition` instead) - `concatMap` (use `flatMap` instead) - `bind` (use `flatMap` instead) - `bindWithIndex` (use `flatMapWithIndex` instead) diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index 7f3388c2..4e73dde2 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -59,10 +59,14 @@ extension FpdartOnIterable on Iterable { /// Return a record where first element is longest prefix (possibly empty) of this [Iterable] /// with elements that **do not satisfy** `test` and second element is the remainder of the [Iterable]. - (Iterable, Iterable) partition(bool Function(T t) test) { - final notTest = test.negate; - return (takeWhile(notTest), skipWhile(notTest)); - } + (Iterable, Iterable) breakI(bool Function(T t) test) => + (takeWhile(test.negate), skipWhile(test.negate)); + + /// Return a record containing the values of this [Iterable] + /// for which `test` is `false` in the first element, + /// and the values for which it is `true` in the second element. + (Iterable, Iterable) partition(bool Function(T t) test) => + (where(test.negate), where(test)); /// Return a record where first element is an [Iterable] with the first `n` elements of this [Iterable], /// and the second element contains the rest of the [Iterable]. diff --git a/packages/fpdart/test/src/extension/iterable_extension_test.dart b/packages/fpdart/test/src/extension/iterable_extension_test.dart index 8a682d98..de883e77 100644 --- a/packages/fpdart/test/src/extension/iterable_extension_test.dart +++ b/packages/fpdart/test/src/extension/iterable_extension_test.dart @@ -229,6 +229,20 @@ void main() { expect(ap.$2.elementAt(3), 4); }); + test('breakI', () { + final list1 = [4, 5, 1, 3, 4]; + final ap = list1.breakI((t) => t < 3); + + expect(ap.$1.length, 2); + expect(ap.$1.elementAt(0), 4); + expect(ap.$1.elementAt(1), 5); + + expect(ap.$2.length, 3); + expect(ap.$2.elementAt(0), 1); + expect(ap.$2.elementAt(1), 3); + expect(ap.$2.elementAt(2), 4); + }); + test('splitAt', () { final list1 = [1, 2, 3, 4]; final ap = list1.splitAt(2); @@ -302,15 +316,16 @@ void main() { test('partition', () { final list1 = [2, 4, 5, 6, 1, 3]; final ap = list1.partition((t) => t > 2); - expect(ap.$1.length, 1); + + expect(ap.$1.length, 2); expect(ap.$1.elementAt(0), 2); + expect(ap.$1.elementAt(1), 1); - expect(ap.$2.length, 5); + expect(ap.$2.length, 4); expect(ap.$2.elementAt(0), 4); expect(ap.$2.elementAt(1), 5); expect(ap.$2.elementAt(2), 6); - expect(ap.$2.elementAt(3), 1); - expect(ap.$2.elementAt(4), 3); + expect(ap.$2.elementAt(3), 3); }); group('all', () { From 7c7dc10bfac50f7454799ed0d97e04f867de0ca1 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sun, 21 May 2023 11:44:48 +0200 Subject: [PATCH 37/71] added difference to Iterable --- packages/fpdart/CHANGELOG.md | 1 + .../fpdart/lib/src/extension/iterable_extension.dart | 11 +++++++++++ .../test/src/extension/iterable_extension_test.dart | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index bc837b9a..19e93530 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -24,6 +24,7 @@ - Added the following methods - `prependAll` (`Iterable`) - `intersperse` (`Iterable`) + - `difference` (`Iterable`) - Fixed the following methods ⚠️ - `takeWhileRight`: Result `List` reversed - `dropWhileRight`: Result `List` reversed diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index 4e73dde2..07288421 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -1,6 +1,7 @@ import '../date.dart'; import '../function.dart'; import '../option.dart'; +import '../typeclass/eq.dart'; import '../typeclass/order.dart'; import 'predicate_extension.dart'; @@ -296,6 +297,16 @@ extension FpdartOnIterable on Iterable { } } + /// Returns an [Iterable] containing the values of this [Iterable] not included + /// in `other` based on `eq`. + Iterable difference(Eq eq, Iterable other) sync* { + for (var element in this) { + if (!other.any((e) => eq.eqv(e, element))) { + yield element; + } + } + } + /// Return an [Iterable] placing an `middle` in between elements of the this [Iterable]. Iterable intersperse(T middle) sync* { if (isNotEmpty) { diff --git a/packages/fpdart/test/src/extension/iterable_extension_test.dart b/packages/fpdart/test/src/extension/iterable_extension_test.dart index de883e77..6010fe27 100644 --- a/packages/fpdart/test/src/extension/iterable_extension_test.dart +++ b/packages/fpdart/test/src/extension/iterable_extension_test.dart @@ -146,6 +146,16 @@ void main() { expect(eq(ap, [1, 2, 3]), true); }); + test('difference', () { + final list1 = [1, 2, 3]; + final ap = list1.difference( + Eq.instance((a1, a2) => a1 == a2), + [2, 3, 4], + ); + + expect(eq(ap, [1]), true); + }); + test('intersperse', () { final ap = [1, 2, 3].intersperse(10); From 8b440ba94d880eafa3046dcf799768480758ce65 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sun, 21 May 2023 11:51:22 +0200 Subject: [PATCH 38/71] added Eq instances --- packages/fpdart/CHANGELOG.md | 4 ++++ packages/fpdart/lib/src/typeclass/eq.dart | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 19e93530..fb19f89d 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -100,6 +100,10 @@ final add = addFunction.curry; [1, 2, 3].map(add(1)); // returns [2, 3, 4] [1, 2, 3].map(addFunction.curry(1)); // returns [2, 3, 4] ``` +- Added `Eq` instances for `num`, `int`, `double`, `String`, and `bool` +```dart +[1, 2, 3].difference(Eq.eqInt(), [2, 3, 4]); /// `[1]` +``` - Removed `bool` extension (`match` and `fold`), use the ternary operator or pattern matching instead ⚠️ ```dart final boolValue = Random().nextBool(); diff --git a/packages/fpdart/lib/src/typeclass/eq.dart b/packages/fpdart/lib/src/typeclass/eq.dart index fac36311..1220c77f 100644 --- a/packages/fpdart/lib/src/typeclass/eq.dart +++ b/packages/fpdart/lib/src/typeclass/eq.dart @@ -53,6 +53,21 @@ abstract class Eq { /// An `Eq` in which everything is the same /// (calling `eqv` returns always `true`). static Eq allEqual() => _Eq((x, y) => true); + + /// Instance of `Eq` for `num` using the standard `==` operator. + static Eq eqNum() => _Eq((x, y) => x == y); + + /// Instance of `Eq` for `int` using the standard `==` operator. + static Eq eqInt() => _Eq((x, y) => x == y); + + /// Instance of `Eq` for `double` using the standard `==` operator. + static Eq eqDouble() => _Eq((x, y) => x == y); + + /// Instance of `Eq` for `String` using the standard `==` operator. + static Eq eqString() => _Eq((x, y) => x == y); + + /// Instance of `Eq` for `bool` using the standard `==` operator. + static Eq eqBool() => _Eq((x, y) => x == y); } class _Eq extends Eq { From a8fbe99fdffc28138e0aa504a155ca07056a9d27 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sun, 21 May 2023 12:49:06 +0200 Subject: [PATCH 39/71] added methods to Eq and Order --- packages/fpdart/CHANGELOG.md | 49 ++++++++ .../fpdart/example/src/typeclass/eq/eq1.dart | 19 +++ .../example/src/typeclass/order/order2.dart | 19 +++ packages/fpdart/lib/src/typeclass/eq.dart | 22 ++++ packages/fpdart/lib/src/typeclass/order.dart | 57 ++++++++- .../lib/src/typeclass/partial_order.dart | 5 + packages/fpdart/test/src/eq_test.dart | 107 ++++++++++++++++ packages/fpdart/test/src/order_test.dart | 115 +++++++++++++++++- .../fpdart/test/src/partial_order_test.dart | 1 - 9 files changed, 386 insertions(+), 8 deletions(-) create mode 100644 packages/fpdart/example/src/typeclass/eq/eq1.dart create mode 100644 packages/fpdart/example/src/typeclass/order/order2.dart diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index fb19f89d..b3e6c321 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -104,6 +104,55 @@ final add = addFunction.curry; ```dart [1, 2, 3].difference(Eq.eqInt(), [2, 3, 4]); /// `[1]` ``` +- Added new method to `Eq` + - `contramap` +```dart +class Parent { + final int value1; + final double value2; + const Parent(this.value1, this.value2); +} + +/// Equality for values of type [Parent] based on their `value1` ([int]). +final eqParentInt = Eq.eqInt().contramap( + (p) => p.value1, +); + +/// Equality for of type [Parent] based on their `value2` ([double]). +final eqParentDouble = Eq.eqDouble().contramap( + (p) => p.value2, +); +``` +- Changed `reverse` in `Order` from static constructor to getter method +```dart +/// Before +final reversed = Order.reverse(instance); + +/// Before +final reversed = instance.reverse; +``` +- Added `Order` instances for `num`, `int`, `double` +- Added new methods to `Order` + - `between` + - `clamp` + - `contramap` +```dart +class Parent { + final int value1; + final double value2; + const Parent(this.value1, this.value2); +} + +/// Order values of type [Parent] based on their `value1` ([int]). +final orderParentInt = Order.orderInt().contramap( + (p) => p.value1, +); + +/// Order values of type [Parent] based on their `value2` ([double]). +final orderParentDouble = Order.orderDouble().contramap( + (p) => p.value2, +); +``` - Removed `bool` extension (`match` and `fold`), use the ternary operator or pattern matching instead ⚠️ ```dart final boolValue = Random().nextBool(); diff --git a/packages/fpdart/example/src/typeclass/eq/eq1.dart b/packages/fpdart/example/src/typeclass/eq/eq1.dart new file mode 100644 index 00000000..3187973c --- /dev/null +++ b/packages/fpdart/example/src/typeclass/eq/eq1.dart @@ -0,0 +1,19 @@ +import 'package:fpdart/fpdart.dart'; + +class Parent { + final int value1; + final double value2; + const Parent(this.value1, this.value2); +} + +void main() { + /// Equality for values of type [Parent] based on their `value1` ([int]). + final eqParentInt = Eq.eqInt().contramap( + (p) => p.value1, + ); + + /// Equality for of type [Parent] based on their `value2` ([double]). + final eqParentDouble = Eq.eqDouble().contramap( + (p) => p.value2, + ); +} diff --git a/packages/fpdart/example/src/typeclass/order/order2.dart b/packages/fpdart/example/src/typeclass/order/order2.dart new file mode 100644 index 00000000..7b7b269e --- /dev/null +++ b/packages/fpdart/example/src/typeclass/order/order2.dart @@ -0,0 +1,19 @@ +import 'package:fpdart/fpdart.dart'; + +class Parent { + final int value1; + final double value2; + const Parent(this.value1, this.value2); +} + +void main() { + /// Order values of type [Parent] based on their `value1` ([int]). + final orderParentInt = Order.orderInt().contramap( + (p) => p.value1, + ); + + /// Order values of type [Parent] based on their `value2` ([double]). + final orderParentDouble = Order.orderDouble().contramap( + (p) => p.value2, + ); +} diff --git a/packages/fpdart/lib/src/typeclass/eq.dart b/packages/fpdart/lib/src/typeclass/eq.dart index 1220c77f..b19af906 100644 --- a/packages/fpdart/lib/src/typeclass/eq.dart +++ b/packages/fpdart/lib/src/typeclass/eq.dart @@ -12,6 +12,28 @@ abstract class Eq { /// Returns `false` if `x` and `y` are equivalent, `true` otherwise. bool neqv(T x, T y) => !eqv(x, y); + /// Return an [Eq] instance based on a parameter of type `T` extracted from a class `A`. + /// ```dart + /// class Parent { + /// final int value1; + /// final double value2; + /// const Parent(this.value1, this.value2); + /// } + + /// /// Equality for values of type [Parent] based on their `value1` ([int]). + /// final eqParentInt = Eq.eqInt().contramap( + /// (p) => p.value1, + /// ); + + /// /// Equality for of type [Parent] based on their `value2` ([double]). + /// final eqParentDouble = Eq.eqDouble().contramap( + /// (p) => p.value2, + /// ); + /// ``` + Eq contramap(T Function(A) map) => _Eq( + (a1, a2) => eqv(map(a1), map(a2)), + ); + /// Convert an implicit `Eq` to an `Eq` using the given function `f`. /// /// ```dart diff --git a/packages/fpdart/lib/src/typeclass/order.dart b/packages/fpdart/lib/src/typeclass/order.dart index 9ed3a50c..11826d61 100644 --- a/packages/fpdart/lib/src/typeclass/order.dart +++ b/packages/fpdart/lib/src/typeclass/order.dart @@ -36,6 +36,38 @@ abstract class Order extends PartialOrder { /// If `x > y`, return `x`, else return `y`. T max(T x, T y) => gt(x, y) ? x : y; + /// Test whether `value` is between `min` and `max` (**inclusive**). + bool between(T min, T max, T value) => gteqv(value, min) && lteqv(value, max); + + /// Clamp `value` between `min` and `max`. + T clamp(T min, T max, T value) => this.max(this.min(value, max), min); + + /// Return an [Order] instance based on a parameter of type `T` extracted from a class `A`. + /// ```dart + /// class Parent { + /// final int value1; + /// final double value2; + /// const Parent(this.value1, this.value2); + /// } + /// + /// /// Order values of type [Parent] based on their `value1` ([int]). + /// final orderParentInt = Order.orderInt().contramap( + /// (p) => p.value1, + /// ); + /// + /// /// Order values of type [Parent] based on their `value2` ([double]). + /// final orderParentDouble = Order.orderDouble().contramap( + /// (p) => p.value2, + /// ); + /// ``` + @override + Order contramap(T Function(A) map) => Order.from( + (a1, a2) => compare(map(a1), map(a2)), + ); + + /// Return an [Order] reversed. + Order get reverse => _Order((x, y) => compare(y, x)); + @override bool eqv(T x, T y) => compare(x, y) == 0; @@ -56,10 +88,6 @@ abstract class Order extends PartialOrder { static Order by(B Function(A a) f, Order ord) => _Order((x, y) => ord.compare(f(x), f(y))); - /// Defines an ordering on `A` from the given order such that all arrows switch direction. - static Order reverse(Order ord) => - _Order((x, y) => ord.compare(y, x)); - /// Returns a new `Order` instance that first compares by the first /// `Order` instance and uses the second `Order` instance to "break ties". /// @@ -85,6 +113,27 @@ abstract class Order extends PartialOrder { /// An `Order` instance that considers all `A` instances to be equal /// (`compare` always returns `0`). static Order allEqual() => _Order((x, y) => 0); + + /// Instance of `Eq` for `int`. + static Order orderInt() => _Order((x, y) => x == y + ? 0 + : x > y + ? 1 + : -1); + + /// Instance of `Eq` for `num`. + static Order orderNum() => _Order((x, y) => x == y + ? 0 + : x > y + ? 1 + : -1); + + /// Instance of `Eq` for `double`. + static Order orderDouble() => _Order((x, y) => x == y + ? 0 + : x > y + ? 1 + : -1); } class _Order extends Order { diff --git a/packages/fpdart/lib/src/typeclass/partial_order.dart b/packages/fpdart/lib/src/typeclass/partial_order.dart index 3073cacc..490afe14 100644 --- a/packages/fpdart/lib/src/typeclass/partial_order.dart +++ b/packages/fpdart/lib/src/typeclass/partial_order.dart @@ -35,6 +35,11 @@ abstract class PartialOrder extends Eq { /// - positive iff `x > y` double? partialCompare(T x, T y); + @override + PartialOrder contramap(T Function(A) map) => PartialOrder.from( + (a1, a2) => partialCompare(map(a1), map(a2)), + ); + /// Returns `true` if `x` == `y`, `false` otherwise. @override bool eqv(T x, T y) { diff --git a/packages/fpdart/test/src/eq_test.dart b/packages/fpdart/test/src/eq_test.dart index 2f74d20c..4895477e 100644 --- a/packages/fpdart/test/src/eq_test.dart +++ b/packages/fpdart/test/src/eq_test.dart @@ -1,6 +1,12 @@ import 'package:fpdart/fpdart.dart'; import 'package:test/test.dart'; +class _Parent { + final int value1; + final double value2; + const _Parent(this.value1, this.value2); +} + void main() { group('Eq', () { test('.instance (int)', () { @@ -66,5 +72,106 @@ void main() { expect(by.eqv('abc', 'abc'), true); expect(by.eqv('abc', 'ab'), false); }); + + test('.eqNum', () { + final eq = Eq.eqNum(); + expect(eq.eqv(10, 10), true); + expect(eq.eqv(10.0, 10), true); + expect(eq.eqv(10.5, 10.5), true); + expect(eq.eqv(-10, -10.0), true); + expect(eq.eqv(10, 10.5), false); + }); + + test('.eqInt', () { + final eq = Eq.eqInt(); + expect(eq.eqv(10, 10), true); + expect(eq.eqv(11, 10), false); + expect(eq.eqv(-10, -10), true); + expect(eq.eqv(10, 11), false); + }); + + test('.eqDouble', () { + final eq = Eq.eqDouble(); + expect(eq.eqv(10, 10), true); + expect(eq.eqv(10.0, 10), true); + expect(eq.eqv(10.5, 10.5), true); + expect(eq.eqv(-10, -10.0), true); + expect(eq.eqv(10, 10.5), false); + }); + + test('.eqString', () { + final eq = Eq.eqString(); + expect(eq.eqv("abc", "abc"), true); + expect(eq.eqv("abc", "abd"), false); + expect(eq.eqv("abc", "ab"), false); + expect(eq.eqv("a", "a"), true); + expect(eq.eqv("a", "ab"), false); + }); + + test('.eqBool', () { + final eq = Eq.eqBool(); + expect(eq.eqv(true, true), true); + expect(eq.eqv(false, true), false); + expect(eq.eqv(true, false), false); + expect(eq.eqv(false, false), true); + }); + + group('contramap', () { + test('int', () { + final eqParentInt = Eq.eqInt().contramap<_Parent>( + (p) => p.value1, + ); + + expect( + eqParentInt.eqv( + _Parent(1, 2.5), + _Parent(1, 12.5), + ), + true, + ); + expect( + eqParentInt.eqv( + _Parent(1, 2.5), + _Parent(4, 2.5), + ), + false, + ); + expect( + eqParentInt.eqv( + _Parent(-1, 2.5), + _Parent(1, 12.5), + ), + false, + ); + }); + + test('double', () { + final eqParentDouble = Eq.eqDouble().contramap<_Parent>( + (p) => p.value2, + ); + + expect( + eqParentDouble.eqv( + _Parent(1, 2.5), + _Parent(1, 2.5), + ), + true, + ); + expect( + eqParentDouble.eqv( + _Parent(1, 2.5), + _Parent(1, 12.5), + ), + false, + ); + expect( + eqParentDouble.eqv( + _Parent(-1, 2.5), + _Parent(1, 2), + ), + false, + ); + }); + }); }); } diff --git a/packages/fpdart/test/src/order_test.dart b/packages/fpdart/test/src/order_test.dart index ce85be8d..b1eee3be 100644 --- a/packages/fpdart/test/src/order_test.dart +++ b/packages/fpdart/test/src/order_test.dart @@ -1,6 +1,12 @@ import 'package:fpdart/fpdart.dart'; import 'package:test/test.dart'; +class _Parent { + final int value1; + final double value2; + const _Parent(this.value1, this.value2); +} + void main() { group('Order', () { group('is a', () { @@ -22,9 +28,9 @@ void main() { expect(instance.compare(2, 1), 1); }); - test('.reverse', () { - final instance = Order.from((a1, a2) => a1.compareTo(a2)); - final reverse = Order.reverse(instance); + test('reverse', () { + final instance = Order.orderInt(); + final reverse = instance.reverse; expect(reverse.compare(1, 1), 0); expect(reverse.compare(1, 2), 1); expect(reverse.compare(2, 1), -1); @@ -116,5 +122,108 @@ void main() { expect(instance.gt(0, 0), false); expect(instance.gt(0, -1), true); }); + + test('between', () { + final instance = Order.orderInt(); + expect(instance.between(0, 10, 4), true); + expect(instance.between(0, 0, 0), true); + expect(instance.between(-1, 0, 0), true); + expect(instance.between(0, 10, 11), false); + expect(instance.between(0, 10, -1), false); + }); + + test('clamp', () { + final instance = Order.orderInt(); + expect(instance.clamp(1, 10, 2), 2); + expect(instance.clamp(1, 10, 10), 10); + expect(instance.clamp(1, 10, 20), 10); + expect(instance.clamp(1, 10, 1), 1); + expect(instance.clamp(1, 10, -10), 1); + }); + + test('orderNum', () { + final ord = Order.orderNum(); + expect(ord.eqv(10, 10), true); + expect(ord.eqv(10.0, 10), true); + expect(ord.gt(10, 0), true); + expect(ord.gt(0, 10), false); + expect(ord.lt(0, 10), true); + }); + + test('orderDouble', () { + final ord = Order.orderDouble(); + expect(ord.eqv(10.5, 10.5), true); + expect(ord.eqv(10.0, 10), true); + expect(ord.gt(1.5, 1.2), true); + expect(ord.gt(1.001, 1.005), false); + expect(ord.lt(0.5, 1.2), true); + }); + + test('orderInt', () { + final ord = Order.orderInt(); + expect(ord.eqv(10, 10), true); + expect(ord.eqv(-10, 10), false); + expect(ord.gt(1, 1), false); + expect(ord.gt(10, 1), true); + expect(ord.lt(-2, 2), true); + }); + + group('contramap', () { + test('int', () { + final orderParentInt = Order.orderInt().contramap<_Parent>( + (p) => p.value1, + ); + + expect( + orderParentInt.eqv( + _Parent(1, 2.5), + _Parent(1, 12.5), + ), + true, + ); + expect( + orderParentInt.eqv( + _Parent(1, 2.5), + _Parent(4, 2.5), + ), + false, + ); + expect( + orderParentInt.eqv( + _Parent(-1, 2.5), + _Parent(1, 12.5), + ), + false, + ); + }); + + test('double', () { + final orderParentDouble = Order.orderDouble().contramap<_Parent>( + (p) => p.value2, + ); + + expect( + orderParentDouble.eqv( + _Parent(1, 2.5), + _Parent(1, 2.5), + ), + true, + ); + expect( + orderParentDouble.eqv( + _Parent(1, 2.5), + _Parent(1, 12.5), + ), + false, + ); + expect( + orderParentDouble.eqv( + _Parent(-1, 2.5), + _Parent(1, 2), + ), + false, + ); + }); + }); }); } diff --git a/packages/fpdart/test/src/partial_order_test.dart b/packages/fpdart/test/src/partial_order_test.dart index 8cab6a9a..aef2052e 100644 --- a/packages/fpdart/test/src/partial_order_test.dart +++ b/packages/fpdart/test/src/partial_order_test.dart @@ -1,4 +1,3 @@ -// import 'package:fpdart/fpdart.dart'; import 'package:fpdart/fpdart.dart'; import 'package:test/test.dart'; From 48dd13423d09d383c881582bee03ba14f5197fe1 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sun, 21 May 2023 17:39:03 +0200 Subject: [PATCH 40/71] changed Eq methods --- packages/fpdart/CHANGELOG.md | 3 +++ packages/fpdart/lib/src/typeclass/eq.dart | 22 ++++++++++------------ packages/fpdart/test/src/eq_test.dart | 8 ++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index b3e6c321..06723083 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -100,6 +100,9 @@ final add = addFunction.curry; [1, 2, 3].map(add(1)); // returns [2, 3, 4] [1, 2, 3].map(addFunction.curry(1)); // returns [2, 3, 4] ``` +- Changed `Eq` static constructors to methods + - `or` + - `and` - Added `Eq` instances for `num`, `int`, `double`, `String`, and `bool` ```dart [1, 2, 3].difference(Eq.eqInt(), [2, 3, 4]); /// `[1]` diff --git a/packages/fpdart/lib/src/typeclass/eq.dart b/packages/fpdart/lib/src/typeclass/eq.dart index b19af906..9ac4c3de 100644 --- a/packages/fpdart/lib/src/typeclass/eq.dart +++ b/packages/fpdart/lib/src/typeclass/eq.dart @@ -12,6 +12,16 @@ abstract class Eq { /// Returns `false` if `x` and `y` are equivalent, `true` otherwise. bool neqv(T x, T y) => !eqv(x, y); + /// Return an `Eq` that gives the result of the **and** of `eq1` and `eq2`. + Eq and(Eq eq) => _Eq( + (x, y) => eqv(x, y) && eq.eqv(x, y), + ); + + /// Return an `Eq` that gives the result of the **or** of this [Eq] and `eq`. + Eq or(Eq eq) => _Eq( + (x, y) => eqv(x, y) || eq.eqv(x, y), + ); + /// Return an [Eq] instance based on a parameter of type `T` extracted from a class `A`. /// ```dart /// class Parent { @@ -47,18 +57,6 @@ abstract class Eq { static Eq by(B Function(A a) f, Eq eq) => _Eq((x, y) => eq.eqv(f(x), f(y))); - /// Return an `Eq` that gives the result of the **and** of `eq1` and `eq2`. - /// - /// Note this is idempotent. - static Eq and(Eq eq1, Eq eq2) => - _Eq((x, y) => eq1.eqv(x, y) && eq2.eqv(x, y)); - - /// Return an `Eq` that gives the result of the **or** of `eq1` and `eq2`. - /// - /// Note this is idempotent. - static Eq or(Eq eq1, Eq eq2) => - _Eq((x, y) => eq1.eqv(x, y) || eq2.eqv(x, y)); - /// Create an `Eq` instance from an `eqv` implementation. /// /// ```dart diff --git a/packages/fpdart/test/src/eq_test.dart b/packages/fpdart/test/src/eq_test.dart index 4895477e..15507ac6 100644 --- a/packages/fpdart/test/src/eq_test.dart +++ b/packages/fpdart/test/src/eq_test.dart @@ -30,12 +30,12 @@ void main() { expect(instance.eqv('abc', 'acb'), false); }); - test('.and', () { + test('and', () { final instance1 = Eq.instance( (a1, a2) => a1.substring(0, 2) == a2.substring(0, 2)); final instance2 = Eq.instance( (a1, a2) => a1.substring(2, 4) == a2.substring(2, 4)); - final and = Eq.and(instance1, instance2); + final and = instance1.and(instance2); expect(instance1.eqv('abef', 'abcd'), true); expect(instance2.eqv('abef', 'zxef'), true); expect(and.eqv('abcd', 'abcd'), true); @@ -43,10 +43,10 @@ void main() { expect(and.eqv('bacd', 'abcd'), false); }); - test('.or', () { + test('or', () { final instance1 = Eq.instance((a1, a2) => a1 == (a2 + 2)); final instance2 = Eq.instance((a1, a2) => a1 == (a2 + 3)); - final or = Eq.or(instance1, instance2); + final or = instance1.or(instance2); expect(or.eqv(2, 1), false); expect(or.eqv(3, 1), true); expect(or.eqv(4, 1), true); From 23ac07c41fc2f7168e81c7c209fbdfba744a83ee Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sun, 21 May 2023 17:46:27 +0200 Subject: [PATCH 41/71] xor to Eq --- packages/fpdart/CHANGELOG.md | 1 + packages/fpdart/lib/src/typeclass/eq.dart | 13 +++++++++++-- packages/fpdart/test/src/eq_test.dart | 11 +++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 06723083..f6f9146f 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -103,6 +103,7 @@ final add = addFunction.curry; - Changed `Eq` static constructors to methods - `or` - `and` +- Added `xor` method to `Eq` - Added `Eq` instances for `num`, `int`, `double`, `String`, and `bool` ```dart [1, 2, 3].difference(Eq.eqInt(), [2, 3, 4]); /// `[1]` diff --git a/packages/fpdart/lib/src/typeclass/eq.dart b/packages/fpdart/lib/src/typeclass/eq.dart index 9ac4c3de..45201a5c 100644 --- a/packages/fpdart/lib/src/typeclass/eq.dart +++ b/packages/fpdart/lib/src/typeclass/eq.dart @@ -12,16 +12,25 @@ abstract class Eq { /// Returns `false` if `x` and `y` are equivalent, `true` otherwise. bool neqv(T x, T y) => !eqv(x, y); - /// Return an `Eq` that gives the result of the **and** of `eq1` and `eq2`. + /// Return an `Eq` that gives the result of **and** of `eq1` and `eq2`. Eq and(Eq eq) => _Eq( (x, y) => eqv(x, y) && eq.eqv(x, y), ); - /// Return an `Eq` that gives the result of the **or** of this [Eq] and `eq`. + /// Return an `Eq` that gives the result of **or** of this [Eq] and `eq`. Eq or(Eq eq) => _Eq( (x, y) => eqv(x, y) || eq.eqv(x, y), ); + /// Return an `Eq` that gives the result of **xor** of this [Eq] and `eq`. + Eq xor(Eq eq) => _Eq( + (x, y) { + final eqThis = eqv(x, y); + final eqOther = eq.eqv(x, y); + return eqThis ? !eqOther : eqOther; + }, + ); + /// Return an [Eq] instance based on a parameter of type `T` extracted from a class `A`. /// ```dart /// class Parent { diff --git a/packages/fpdart/test/src/eq_test.dart b/packages/fpdart/test/src/eq_test.dart index 15507ac6..573ac8ba 100644 --- a/packages/fpdart/test/src/eq_test.dart +++ b/packages/fpdart/test/src/eq_test.dart @@ -53,6 +53,17 @@ void main() { expect(or.eqv(5, 1), false); }); + test('xor', () { + final instance1 = Eq.instance((a1, a2) => a1 == (a2 + 2)); + final instance2 = Eq.instance((a1, a2) => a1 == (a2 + 3)); + final xor = instance1.xor(instance2); + final xorSame = instance1.xor(instance1); + expect(xor.eqv(2, 1), false); + expect(xor.eqv(3, 1), true); + expect(xor.eqv(4, 1), true); + expect(xorSame.eqv(3, 1), false); + }); + test('.fromUniversalEquals', () { final instance = Eq.fromUniversalEquals(); expect(instance.eqv(1, 1), true); From bb86d7f7a9f9f51b834b84bd4fd187d4b35b18a2 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sun, 21 May 2023 18:13:29 +0200 Subject: [PATCH 42/71] predicate as extension methods --- packages/fpdart/CHANGELOG.md | 15 +++- .../example/src/predicate/overview.dart | 9 +++ packages/fpdart/lib/fpdart.dart | 1 - .../src/extension/predicate_extension.dart | 37 +++++++++ packages/fpdart/lib/src/predicate.dart | 60 --------------- .../extension/predicate_extension_test.dart | 70 +++++++++++++++++ packages/fpdart/test/src/predicate_test.dart | 77 ------------------- 7 files changed, 128 insertions(+), 141 deletions(-) create mode 100644 packages/fpdart/example/src/predicate/overview.dart delete mode 100644 packages/fpdart/lib/src/predicate.dart delete mode 100644 packages/fpdart/test/src/predicate_test.dart diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index f6f9146f..2d89a5e8 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -5,7 +5,6 @@ - You can now use exhaustive pattern matching (`None` or `Some`) - Types marked as `final` (no `extends` nor `implements`) - `Unit` - - `Predicate` - `Reader` - `State` - `StateAsync` @@ -75,6 +74,16 @@ Option getRandomOption(T value) => randomBool .map((isValid) => isValid ? some(value) : none()) .run(); ``` +- Removed `Predicate` class and added extension methods in its place ⚠️ +```dart +bool isEven(int n) => n % 2 == 0; +bool isDivisibleBy3(int n) => n % 3 == 0; + +final isOdd = isEven.negate; +final isEvenAndDivisibleBy3 = isEven.and(isDivisibleBy3); +final isEvenOrDivisibleBy3 = isEven.or(isDivisibleBy3); +final isStringWithEvenLength = isEven.contramap((n) => n.length); +``` - Updated curry / uncarry extensions ⚠️ - Renamed `curry` to `curryAll` for functions with 3, 4, 5 parameters - Changed definition of `curry` to curry only the first parameter @@ -169,8 +178,8 @@ final result = boolValue.fold(() => -1, () => 1); final result = boolValue ? 1 : -1; final result = switch (boolValue) { true => 1, false => -1 }; ``` -- Removed `id` and `idFuture`, use `identity` and `identityFuture` instead ⚠️ -- Removed `idFirst` and `idSecond` functions ⚠️ +- Removed global `id` and `idFuture`, use `identity` and `identityFuture` instead ⚠️ +- Removed global `idFirst` and `idSecond` functions ⚠️ - Removed `Compose` class and extension methods ⚠️ - Removed extension methods on nullable types (`toOption`, `toEither`, `toTaskOption`, `toIOEither`, `toTaskEither`, `toTaskEitherAsync`) ⚠️ - Organized all extensions inside internal `extension` folder diff --git a/packages/fpdart/example/src/predicate/overview.dart b/packages/fpdart/example/src/predicate/overview.dart new file mode 100644 index 00000000..af75530b --- /dev/null +++ b/packages/fpdart/example/src/predicate/overview.dart @@ -0,0 +1,9 @@ +import 'package:fpdart/fpdart.dart'; + +bool isEven(int n) => n % 2 == 0; +bool isDivisibleBy3(int n) => n % 3 == 0; + +final isOdd = isEven.negate; +final isEvenAndDivisibleBy3 = isEven.and(isDivisibleBy3); +final isEvenOrDivisibleBy3 = isEven.or(isDivisibleBy3); +final isStringWithEvenLength = isEven.contramap((n) => n.length); diff --git a/packages/fpdart/lib/fpdart.dart b/packages/fpdart/lib/fpdart.dart index 963f9d92..9770d9fd 100644 --- a/packages/fpdart/lib/fpdart.dart +++ b/packages/fpdart/lib/fpdart.dart @@ -7,7 +7,6 @@ export 'src/io_either.dart'; export 'src/io_option.dart'; export 'src/io_ref.dart'; export 'src/option.dart'; -export 'src/predicate.dart'; export 'src/random.dart'; export 'src/reader.dart'; export 'src/state.dart'; diff --git a/packages/fpdart/lib/src/extension/predicate_extension.dart b/packages/fpdart/lib/src/extension/predicate_extension.dart index e9f9d4ae..b3825ff1 100644 --- a/packages/fpdart/lib/src/extension/predicate_extension.dart +++ b/packages/fpdart/lib/src/extension/predicate_extension.dart @@ -5,6 +5,19 @@ extension FpdartOnPredicate on bool Function() { /// final alwaysFalse = alwaysTrue.negate; /// ``` bool get negate => !this(); + + /// Compose using `&&` this function with `predicate`. + bool Function() and(bool Function() predicate) => () => this() && predicate(); + + /// Compose using `||` this function with `predicate`. + bool Function() or(bool Function() predicate) => () => this() || predicate(); + + /// Compose **xor** this function with `predicate`. + bool Function() xor(bool Function() predicate) => () { + final thisPredicate = this(); + final otherPredicate = predicate(); + return thisPredicate ? !otherPredicate : otherPredicate; + }; } extension FpdartOnPredicate1

on bool Function(P) { @@ -14,4 +27,28 @@ extension FpdartOnPredicate1

on bool Function(P) { /// final isOdd = isEven.negate; /// ``` bool Function(P) get negate => (p) => !this(p); + + /// Compose using `&&` this function with `predicate`. + bool Function(P) and(bool Function(P p) predicate) => + (p) => this(p) && predicate(p); + + /// Compose using `||` this function with `predicate`. + bool Function(P) or(bool Function(P) predicate) => + (p) => this(p) || predicate(p); + + /// Compose **xor** this function with `predicate`. + bool Function(P) xor(bool Function(P) predicate) => (p) { + final thisPredicate = this(p); + final otherPredicate = predicate(p); + return thisPredicate ? !otherPredicate : otherPredicate; + }; + + /// Apply `map` to the value of the parameter `P` and return a new `bool Function(A)`. + /// + /// Similar to `map` for functions that return `bool`. + /// ```dart + /// bool even(int n) => n % 2 == 0; + /// final evenLength = even.contramap((a) => a.length); + /// ``` + bool Function(A) contramap(P Function(A a) map) => (a) => this(map(a)); } diff --git a/packages/fpdart/lib/src/predicate.dart b/packages/fpdart/lib/src/predicate.dart deleted file mode 100644 index 8022b2ff..00000000 --- a/packages/fpdart/lib/src/predicate.dart +++ /dev/null @@ -1,60 +0,0 @@ -/// Compose functions that given a generic value [T] return a `bool`. -final class Predicate { - final bool Function(T t) _predicate; - - /// Build a [Predicate] given a function returning a `bool`. - const Predicate(this._predicate); - - /// Run the predicate and extract its [bool] value given `t`. - bool call(T t) => _predicate(t); - - /// Apply `fun` to the value of this `Predicate` and return a new `Predicate`. - /// - /// Similar to `map` for [Predicate]s. - Predicate contramap(T Function(A a) fun) => - Predicate((a) => _predicate(fun(a))); - - /// Compose this [Predicate] with the given `predicate` using **AND**. - /// - /// You can also use the `&` operator. - Predicate and(Predicate predicate) => - Predicate((t) => _predicate(t) && predicate(t)); - - /// Compose this [Predicate] with the given `predicate` using **AND**. - Predicate operator &(Predicate predicate) => and(predicate); - - /// Compose this [Predicate] with the given `predicate` using **OR**. - /// - /// You can also use the `|` operator. - Predicate or(Predicate predicate) => - Predicate((t) => _predicate(t) || predicate(t)); - - /// Compose this [Predicate] with the given `predicate` using **OR**. - Predicate operator |(Predicate predicate) => or(predicate); - - /// Compose this [Predicate] with the given `predicate` using **XOR**. - /// - /// You can also use the `^` operator. - Predicate xor(Predicate predicate) => Predicate((t) { - final boolThis = _predicate(t); - final boolOther = predicate(t); - return boolThis ? !boolOther : boolOther; - }); - - /// Compose this [Predicate] with the given `predicate` using **XOR**. - Predicate operator ^(Predicate predicate) => xor(predicate); - - /// Compose this [Predicate] with the given `predicate` using **NOT** (**!**). - /// - /// You can also use the `~` operator. - Predicate get not => Predicate((t) => !_predicate(t)); - - /// Build a [Predicate] that returns **NOT** (**!**) of the given `predicate`. - /// - /// You can also use the `~` operator. - factory Predicate.not(bool Function(T) predicate) => - Predicate((t) => !predicate(t)); - - /// Build a [Predicate] that returns **NOT** (**!**) of the given `predicate`. - Predicate operator ~() => Predicate((t) => !_predicate(t)); -} diff --git a/packages/fpdart/test/src/extension/predicate_extension_test.dart b/packages/fpdart/test/src/extension/predicate_extension_test.dart index cd55c873..5d1c79d7 100644 --- a/packages/fpdart/test/src/extension/predicate_extension_test.dart +++ b/packages/fpdart/test/src/extension/predicate_extension_test.dart @@ -8,6 +8,33 @@ void main() { bool fun() => boolValue; expect(fun(), !fun.negate); }); + + test('and', () { + bool funTrue() => true; + bool funFalse() => false; + expect(funTrue.and(funTrue)(), true); + expect(funTrue.and(funFalse)(), false); + expect(funFalse.and(funTrue)(), false); + expect(funFalse.and(funFalse)(), false); + }); + + test('or', () { + bool funTrue() => true; + bool funFalse() => false; + expect(funTrue.or(funTrue)(), true); + expect(funTrue.or(funFalse)(), true); + expect(funFalse.or(funTrue)(), true); + expect(funFalse.or(funFalse)(), false); + }); + + test('xor', () { + bool funTrue() => true; + bool funFalse() => false; + expect(funTrue.xor(funTrue)(), false); + expect(funTrue.xor(funFalse)(), true); + expect(funFalse.xor(funTrue)(), true); + expect(funFalse.xor(funFalse)(), false); + }); }); group('FpdartOnPredicate1', () { @@ -15,6 +42,49 @@ void main() { bool fun(int n) => n % 2 == 0; expect(fun(intValue), !fun.negate(intValue)); }); + + test('and', () { + bool fun2(int n) => n % 2 == 0; + bool fun3(int n) => n % 3 == 0; + expect(fun2.and(fun2)(4), true); + expect(fun2.and(fun3)(4), false); + expect(fun2.and(fun3)(6), true); + expect(fun3.and(fun2)(4), false); + expect(fun3.and(fun3)(3), true); + }); + + test('or', () { + bool fun2(int n) => n % 2 == 0; + bool fun3(int n) => n % 3 == 0; + expect(fun2.or(fun2)(4), true); + expect(fun2.or(fun3)(4), true); + expect(fun2.or(fun3)(6), true); + expect(fun3.or(fun2)(4), true); + expect(fun3.or(fun2)(7), false); + expect(fun3.or(fun3)(7), false); + }); + + test('xor', () { + bool fun2(int n) => n % 2 == 0; + bool fun3(int n) => n % 3 == 0; + expect(fun2.xor(fun2)(4), false); + expect(fun2.xor(fun3)(4), true); + expect(fun2.xor(fun3)(6), false); + expect(fun3.xor(fun2)(4), true); + expect(fun3.xor(fun3)(3), false); + expect(fun3.xor(fun2)(7), false); + }); + + test('contramap', () { + bool even(int n) => n % 2 == 0; + final evenLength = even.contramap((a) => a.length); + expect(evenLength("abc"), false); + expect(evenLength("abcd"), true); + expect(evenLength("a"), false); + expect(evenLength("ab"), true); + expect(evenLength("abcde"), false); + expect(evenLength("abcdef"), true); + }); }); }); } diff --git a/packages/fpdart/test/src/predicate_test.dart b/packages/fpdart/test/src/predicate_test.dart deleted file mode 100644 index 06fa81d4..00000000 --- a/packages/fpdart/test/src/predicate_test.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:fpdart/fpdart.dart'; -import 'package:test/test.dart'; - -void main() { - group('Predicate', () { - test('not (factory)', () { - final ap = Predicate.not((a) => a > 10); - expect(ap(12), false); - expect(ap(9), true); - expect(ap(21), false); - }); - - test('and', () { - final pr = Predicate((a) => a > 10); - final ap = pr.and(Predicate((a) => a < 20)); - expect(ap(12), true); - expect(ap(9), false); - expect(ap(21), false); - }); - - test('&', () { - final pr = Predicate((a) => a > 10); - final ap = pr & Predicate((a) => a < 20); - expect(ap(12), true); - expect(ap(9), false); - expect(ap(21), false); - }); - - test('or', () { - final pr = Predicate((a) => a > 10); - final ap = pr.or(Predicate((a) => a == 2)); - expect(ap(12), true); - expect(ap(9), false); - expect(ap(2), true); - }); - - test('|', () { - final pr = Predicate((a) => a > 10); - final ap = pr | Predicate((a) => a == 2); - expect(ap(12), true); - expect(ap(9), false); - expect(ap(2), true); - }); - - test('not', () { - final pr = Predicate((a) => a > 10); - final ap = pr.not; - expect(ap(12), false); - expect(ap(9), true); - expect(ap(2), true); - }); - - test('~', () { - final pr = Predicate((a) => a > 10); - final ap = ~pr; - expect(ap(12), false); - expect(ap(9), true); - expect(ap(2), true); - }); - - test('xor', () { - final pr = Predicate((a) => a > 10); - final ap = pr.xor(Predicate((a) => a < 20)); - expect(ap(12), false); - expect(ap(9), true); - expect(ap(21), true); - }); - - test('^', () { - final pr = Predicate((a) => a > 10); - final ap = pr ^ Predicate((a) => a < 20); - expect(ap(12), false); - expect(ap(9), true); - expect(ap(21), true); - }); - }); -} From 63019f4033421600214543d56c491e6a7cc9514d Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Mon, 22 May 2023 19:36:26 +0200 Subject: [PATCH 43/71] refactor map extension methods --- packages/fpdart/CHANGELOG.md | 30 + .../lib/src/extension/iterable_extension.dart | 11 + .../lib/src/extension/map_extension.dart | 647 +++++++++--------- packages/fpdart/lib/src/typedef.dart | 1 - .../extension/iterable_extension_test.dart | 7 + .../src/extension/map_extension_test.dart | 44 +- 6 files changed, 413 insertions(+), 327 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 2d89a5e8..9649a1cb 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -24,6 +24,7 @@ - `prependAll` (`Iterable`) - `intersperse` (`Iterable`) - `difference` (`Iterable`) + - `filterWithIndex` (`Iterable`) - Fixed the following methods ⚠️ - `takeWhileRight`: Result `List` reversed - `dropWhileRight`: Result `List` reversed @@ -38,6 +39,34 @@ - `bind` (use `flatMap` instead) - `bindWithIndex` (use `flatMapWithIndex` instead) - `concatMapWithIndex` (use `flatMapWithIndex` instead) +- Refactoring of `Map` extension methods + - Improved performance + - Removed the following methods ⚠️ + - `member` (use `containsKey` instead) + - `elem` (use `containsValue` instead) + - `toIterable` (use `toSortedList` instead) + - Updated the following methods ⚠️ + - `toIterable` renamed to `toSortedList` (return a `List` instead of `Iterable`) + - `modifyAt` changed parameter order and no more curried + - `modifyAtIfPresent` changed parameter order and no more curried + - `updateAt` no more curried + - `updateAtIfPresent` no more curried + - `deleteAt` no more curried + - `upsertAt` no more curried + - `pop` no more curried + - `foldLeft` no more curried + - `foldLeftWithKey` no more curried + - `foldLeftWithIndex` no more curried + - `foldLeftWithKeyAndIndex` no more curried + - `foldRight` no more curried + - `foldRightWithKey` no more curried + - `foldRightWithIndex` no more curried + - `foldRightWithKeyAndIndex` no more curried + - `union` no more curried + - `intersection` no more curried + - `isSubmap` no more curried + - `collect` no more curried + - `difference` no more curried - Added conversions helpers from `String` to `num`, `int`, `double`, and `bool` using `Option` and `Either` (both as extension methods on `String` and as functions) - `toNumOption` - `toIntOption` @@ -181,6 +210,7 @@ final result = switch (boolValue) { true => 1, false => -1 }; - Removed global `id` and `idFuture`, use `identity` and `identityFuture` instead ⚠️ - Removed global `idFirst` and `idSecond` functions ⚠️ - Removed `Compose` class and extension methods ⚠️ +- Removed `Magma` typedef ⚠️ - Removed extension methods on nullable types (`toOption`, `toEither`, `toTaskOption`, `toIOEither`, `toTaskEither`, `toTaskEitherAsync`) ⚠️ - Organized all extensions inside internal `extension` folder diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index 07288421..09048d91 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -43,6 +43,17 @@ extension FpdartOnIterable on Iterable { /// Equivalent to `Iterable.where`. Iterable filter(bool Function(T t) test) => where(test); + /// Returns the list of those elements that satisfy `test`. + Iterable filterWithIndex(bool Function(T t, int index) test) sync* { + var index = 0; + for (var value in this) { + if (test(value, index)) { + yield value; + } + index += 1; + } + } + /// Extract all elements **starting from the first** as long as `test` returns `true`. /// /// Equivalent to `Iterable.takeWhile`. diff --git a/packages/fpdart/lib/src/extension/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart index d31a6686..0f7b18be 100644 --- a/packages/fpdart/lib/src/extension/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -1,80 +1,70 @@ import '../option.dart'; import '../typeclass/eq.dart'; import '../typeclass/order.dart'; -import '../typedef.dart'; import 'iterable_extension.dart'; import 'option_extension.dart'; /// Functional programming functions on a mutable dart [Map] using `fpdart`. -extension FpdartOnMutableMap on Map { - /// Convert each **value** of the [Map] using the `update` function and returns a new [Map]. - Map mapValue(A Function(V value) update) => - map((key, value) => MapEntry(key, update(value))); - - /// Convert each **value** of the [Map] using the `update` function and returns a new [Map]. - Map mapWithIndex(A Function(V value, int index) update) { - final entries = this.entries; - final mapped = {}; - var i = 0; - for (final item in entries) { - mapped.addEntries([MapEntry(item.key, update(item.value, i))]); - i += 1; - } - return mapped; - } - - /// Returns the list of those elements of the [Map] whose **value** satisfies `predicate`. - Map filter(bool Function(V value) predicate) { - final entries = this.entries; - final filteredMap = {}; - for (final item in entries) { - if (predicate(item.value)) { - filteredMap.addEntries([item]); - } - } - return filteredMap; - } - - /// Returns the list of those elements of the [Map] whose **value** satisfies `predicate`. - Map filterWithIndex(bool Function(V value, int index) predicate) { - final entries = this.entries; - final filteredMap = {}; - var i = 0; - for (final item in entries) { - if (predicate(item.value, i)) { - filteredMap.addEntries([item]); - } - i += 1; - } - return filteredMap; - } - - /// Returns the list of those elements of the [Map] whose key/value satisfies `predicate`. - Map filterWithKey(bool Function(K key, V value) predicate) { - final entries = this.entries; - final filteredMap = {}; - for (final item in entries) { - if (predicate(item.key, item.value)) { - filteredMap.addEntries([item]); - } - } - return filteredMap; - } +extension FpdartOnMap on Map { + /// Return number of keys in the [Map] (`keys.length`). + int get size => keys.length; - /// Returns the list of those elements of the [Map] whose key/value satisfies `predicate`. + /// Convert each **value** of the [Map] using + /// the `update` function and returns a new [Map]. + /// + /// Immutable version of `Map.updateAll`. + Map mapValue(A Function(V value) update) => map( + (key, value) => MapEntry(key, update(value)), + ); + + /// Convert each **value** of the [Map] using + /// the `update` function and returns a new [Map]. + Map mapWithIndex(A Function(V value, int index) update) => + Map.fromEntries( + entries.mapWithIndex( + (entry, index) => MapEntry( + entry.key, + update(entry.value, index), + ), + ), + ); + + /// Returns the list of those elements of the [Map] whose **value** satisfies `test`. + Map filter(bool Function(V value) test) => Map.fromEntries( + entries.where( + (entry) => test(entry.value), + ), + ); + + /// Returns the list of those elements of the [Map] whose **value** satisfies `test`. + Map filterWithIndex(bool Function(V value, int index) test) => + Map.fromEntries( + entries.filterWithIndex( + (entry, index) => test(entry.value, index), + ), + ); + + /// Returns the list of those elements of the [Map] whose key/value satisfies `test`. + Map filterWithKey(bool Function(K key, V value) test) => + Map.fromEntries( + entries.where( + (entry) => test(entry.key, entry.value), + ), + ); + + /// Returns the list of those elements of the [Map] whose key/value satisfies `test`. Map filterWithKeyAndIndex( - bool Function(K key, V value, int index) predicate) { - final entries = this.entries; - final filteredMap = {}; - var i = 0; - for (final item in entries) { - if (predicate(item.key, item.value, i)) { - filteredMap.addEntries([item]); - } - i += 1; - } - return filteredMap; - } + bool Function(K key, V value, int index) test, + ) => + Map.fromEntries( + entries.filterWithIndex( + (entry, index) => test( + entry.key, + entry.value, + index, + ), + ), + ); /// Get the value at given `key` if present, otherwise return [None]. Option lookup(K key) => Option.fromNullable(this[key]); @@ -82,307 +72,348 @@ extension FpdartOnMutableMap on Map { /// Get the value and key at given `key` if present, otherwise return [None]. Option<(K, V)> lookupWithKey(K key) { final value = this[key]; - return value != null ? some((key, value)) : none(); + return value != null ? some((key, value)) : const None(); } /// Return an [Option] that conditionally accesses map keys, only if they match the /// given type. + /// /// Useful for accessing nested JSON. /// - /// ``` - /// expect( - /// { 'test': 123 }.extract('test'), - /// Option.of(123), - /// ); - /// expect( - /// { 'test': 'string' }.extract('test'), - /// Option.none(), - /// ); + /// ```dart + /// { 'test': 123 }.extract('test'); /// `Some(123)` + /// { 'test': 'string' }.extract('test'); /// `None()` /// ``` Option extract(K key) { final value = this[key]; - if (value is T) return Option.of(value); - return Option.none(); + return value is T ? some(value) : const None(); } - /// Return an [Option] that conditionally accesses map keys, if they contain a map - /// with the same key type. + /// Return an [Option] that conditionally accesses map keys if they contain a key + /// with a [Map] value. + /// /// Useful for accessing nested JSON. /// - /// ``` - /// expect( - /// { 'test': { 'foo': 'bar' } }.extractMap('test'), - /// Option.of({ 'foo': 'bar' }), - /// ); - /// expect( - /// { 'test': 'string' }.extractMap('test'), - /// Option.none(), - /// ); + /// ```dart + /// { 'test': { 'foo': 'bar' } }.extractMap('test'); /// `Some({ 'foo': 'bar' })` + /// { 'test': 'string' }.extractMap('test'); /// `None()` /// ``` Option> extractMap(K key) => extract>(key); - /// Test whether or not `key` exists in this [Map] - /// - /// Same as dart's `containsKey`. - bool member(K key) => containsKey(key); - - /// Test whether or not `value` exists in this [Map] - /// - /// Same as dart's `containsValue`. - bool elem(V value) => containsValue(value); - /// If the given `key` is present in the [Map], then modify its value - /// using `modify` and return a the new [Map]. + /// using `update` and return a the new [Map]. + /// /// Otherwise, return [None]. - Option> Function(K key, V Function(V value)) modifyAt(Eq eq) => - (K key, V Function(V value) modify) => member(key) - ? some( - map( - (k, v) => - eq.eqv(k, key) ? MapEntry(key, modify(v)) : MapEntry(k, v), - ), - ) - : none(); + Option> modifyAt( + Eq eq, + V Function(V value) update, + K key, + ) { + if (containsKey(key)) { + return some( + map( + (k, v) => eq.eqv(k, key) + ? MapEntry( + key, + update(v), + ) + : MapEntry(k, v), + ), + ); + } + + return const None(); + } /// If the given `key` is present in the [Map], then modify its value - /// using `modify` and return a the new [Map]. - /// Otherwise, return the original unmodified [Map]. - Map Function(K key, V Function(V value)) modifyAtIfPresent(Eq eq) => - (K key, V Function(V value) modify) => - modifyAt(eq)(key, modify).getOrElse(() => this); + /// using `update` and return a the new [Map]. + /// + /// Otherwise, return a copy of the original unmodified [Map]. + Map modifyAtIfPresent( + Eq eq, + V Function(V value) update, + K key, + ) => + modifyAt(eq, update, key).getOrElse(() => {...this}); /// If the given `key` is present in the [Map], then update its value to `value`. + /// /// Otherwise, return [None]. - Option> Function(K key, V value) updateAt(Eq eq) => - (K key, V value) => member(key) - ? some( - map( - (k, v) => - eq.eqv(k, key) ? MapEntry(key, value) : MapEntry(k, v), - ), - ) - : none(); + Option> updateAt(Eq eq, K key, V value) { + if (containsKey(key)) { + return some( + map( + (k, v) => eq.eqv(k, key) + ? MapEntry( + key, + value, + ) + : MapEntry(k, v), + ), + ); + } - /// If the given `key` is present in the [Map], then update its value to `value`. - /// Otherwise, return the original unmodified [Map]. - Map Function(K key, V value) updateAtIfPresent(Eq eq) => - (K key, V value) => updateAt(eq)(key, value).getOrElse(() => this); + return const None(); + } - /// Delete value and key at given `key` in the [Map] and return updated [Map]. - Map Function(K key) deleteAt(Eq eq) => - (K key) => filterWithKey((k, v) => !eq.eqv(k, key)); + /// If the given `key` is present in the [Map], then update its value to `value`. + /// Otherwise, return a copy of the original unmodified [Map]. + Map updateAtIfPresent( + Eq eq, + K key, + V value, + ) => + updateAt(eq, key, value).getOrElse( + () => {...this}, + ); + + /// Delete entry at given `key` if present in the [Map] and return updated [Map]. + Map deleteAt(Eq eq, K key) => + filterWithKey((k, v) => !eq.eqv(k, key)); /// Insert or replace a key/value pair in a [Map]. - Map Function(K key, V value) upsertAt(Eq eq) => (K key, V value) { - final newMap = {...this}; - newMap.addEntries([MapEntry(key, value)]); - return newMap.modifyAtIfPresent(eq)(key, (_) => value); - }; + Map upsertAt(Eq eq, K key, V value) => + modifyAtIfPresent(eq, (_) => value, key); - /// Delete a key and value from a this [Map], returning the deleted value as well as the subsequent [Map]. - Option<(V, Map)> Function(K key) pop(Eq eq) => - (K key) => lookup(key).map((v) => (v, deleteAt(eq)(key))); + /// Delete a `key` and value from a this [Map], returning the deleted value as well as the updated [Map]. + /// + /// If `key` is not present, then return [None]. + Option<(V, Map)> pop(Eq eq, K key) => lookup(key).map( + (v) => (v, deleteAt(eq, key)), + ); - /// Apply `fun` to all the values of this [Map] sorted based on `order` on their key, + /// Apply `compose` to all the values of this [Map] sorted based on `order` on their key, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(A acc, V value) fun) foldLeft( - Order order) => - (A initial, A Function(A acc, V value) fun) { - final sorted = toIterable(order); - var result = initial; - for (final item in sorted) { - result = fun(result, item.value); - } - return result; - }; - - /// Apply `fun` to all the values of this [Map] sorted based on `order` on their key, + A foldLeft(Order order, A initial, A Function(A acc, V value) compose) { + final sorted = toSortedList(order); + var result = initial; + for (final item in sorted) { + result = compose(result, item.value); + } + return result; + } + + /// Apply `compose` to all the values of this [Map] sorted based on `order` on their key, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(A acc, K key, V value) fun) - foldLeftWithKey(Order order) => - (A initial, A Function(A acc, K key, V value) fun) { - final sorted = toIterable(order); - var result = initial; - for (final item in sorted) { - result = fun(result, item.key, item.value); - } - return result; - }; - - /// Apply `fun` to all the values of this [Map] sorted based on `order` on their key, + A foldLeftWithKey( + Order order, + A initial, + A Function(A acc, K key, V value) compose, + ) { + final sorted = toSortedList(order); + var result = initial; + for (final item in sorted) { + result = compose(result, item.key, item.value); + } + return result; + } + + /// Apply `compose` to all the values of this [Map] sorted based on `order` on their key, /// passing also the current index of the iteration, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(A acc, V value, int index) fun) - foldLeftWithIndex(Order order) => - (A initial, A Function(A acc, V value, int index) fun) { - final sorted = toIterable(order); - var result = initial; - var i = 0; - for (final item in sorted) { - result = fun(result, item.value, i); - i += 1; - } - return result; - }; - - /// Apply `fun` to all the values of this [Map] sorted based on `order` on their key, + A foldLeftWithIndex( + Order order, + A initial, + A Function(A acc, V value, int index) compose, + ) { + final sorted = toSortedList(order); + var result = initial; + var i = 0; + for (final item in sorted) { + result = compose(result, item.value, i); + i += 1; + } + return result; + } + + /// Apply `compose` to all the values of this [Map] sorted based on `order` on their key, /// passing also the current index of the iteration, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(A acc, K key, V value, int index) fun) - foldLeftWithKeyAndIndex(Order order) => - (A initial, A Function(A acc, K key, V value, int index) fun) { - final sorted = toIterable(order); - var result = initial; - var i = 0; - for (final item in sorted) { - result = fun(result, item.key, item.value, i); - i += 1; - } - return result; - }; - - /// Apply `fun` to all the values of this [Map] sorted based on the inverse of `order` on their key, + A foldLeftWithKeyAndIndex( + Order order, + A initial, + A Function(A acc, K key, V value, int index) compose, + ) { + final sorted = toSortedList(order); + var result = initial; + var i = 0; + for (final item in sorted) { + result = compose(result, item.key, item.value, i); + i += 1; + } + return result; + } + + /// Apply `compose` to all the values of this [Map] sorted based on the inverse of `order` on their key, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(V value, A acc) fun) foldRight( - Order order) => - (A initial, A Function(V value, A acc) fun) { - final sorted = toIterable(order).toList().reversed; - var result = initial; - for (final item in sorted) { - result = fun(item.value, result); - } - return result; - }; - - /// Apply `fun` to all the values of this [Map] sorted based on the inverse of `order` on their key, + A foldRight( + Order order, + A initial, + A Function(V value, A acc) compose, + ) { + final sorted = toSortedList(order).toList().reversed; + var result = initial; + for (final item in sorted) { + result = compose(item.value, result); + } + return result; + } + + /// Apply `compose` to all the values of this [Map] sorted based on the inverse of `order` on their key, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(K key, V value, A acc) fun) - foldRightWithKey(Order order) => - (A initial, A Function(K key, V value, A acc) fun) { - final sorted = toIterable(order).toList().reversed; - var result = initial; - for (final item in sorted) { - result = fun(item.key, item.value, result); - } - return result; - }; - - /// Apply `fun` to all the values of this [Map] sorted based on the inverse of `order` on their key, + A foldRightWithKey( + Order order, + A initial, + A Function(K key, V value, A acc) compose, + ) { + final sorted = toSortedList(order).toList().reversed; + var result = initial; + for (final item in sorted) { + result = compose(item.key, item.value, result); + } + return result; + } + + /// Apply `compose` to all the values of this [Map] sorted based on the inverse of `order` on their key, /// passing also the current index of the iteration, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(V value, A acc, int index) fun) - foldRightWithIndex(Order order) => - (A initial, A Function(V value, A acc, int index) fun) { - final sorted = toIterable(order).toList().reversed; - var result = initial; - var i = 0; - for (final item in sorted) { - result = fun(item.value, result, i); - i += 1; - } - return result; - }; - - /// Apply `fun` to all the values of this [Map] sorted based on the inverse of `order` on their key, + A foldRightWithIndex( + Order order, + A initial, + A Function(V value, A acc, int index) compose, + ) { + final sorted = toSortedList(order).toList().reversed; + var result = initial; + var i = 0; + for (final item in sorted) { + result = compose(item.value, result, i); + i += 1; + } + return result; + } + + /// Apply `compose` to all the values of this [Map] sorted based on the inverse of `order` on their key, /// passing also the current index of the iteration, /// and return the result of combining all the intermediate values. - A Function(A initial, A Function(K key, V value, A acc, int index) fun) - foldRightWithKeyAndIndex(Order order) => - (A initial, A Function(K key, V value, A acc, int index) fun) { - final sorted = toIterable(order).toList().reversed; - var result = initial; - var i = 0; - for (final item in sorted) { - result = fun(item.key, item.value, result, i); - i += 1; - } - return result; - }; - - /// Return number of keys in the [Map]. - int get size => keys.length; - - /// Get a sorted [Iterable] of the key/value pairs contained in this [Map]. - Iterable> toIterable(Order order) => - entries.sortWith((map) => map.key, order); + A foldRightWithKeyAndIndex( + Order order, + A initial, + A Function(K key, V value, A acc, int index) compose, + ) { + final sorted = toSortedList(order).toList().reversed; + var result = initial; + var i = 0; + for (final item in sorted) { + result = compose(item.key, item.value, result, i); + i += 1; + } + return result; + } - /// Combine the key/value of two [Map] using `combine` where the key is the same. - Map Function(Map map) union(Eq eq, Magma combine) => - (Map map) => map.foldLeftWithKey>(Order.allEqual())( - this, - (acc, key, value) => acc - .modifyAt(eq)( - key, - (v) => combine(v, value), - ) - .getOrElse( - () => acc.upsertAt(eq)(key, value), - ), - ); + /// Combine the key/value of this [Map] and `map` using `combine` where the key is the same. + Map union( + Eq eq, + V Function(V x, V y) combine, + Map map, + ) => + map.foldLeftWithKey>( + Order.allEqual(), + {...this}, + (acc, key, value) => acc + .modifyAt( + eq, + (v) => combine(v, value), + key, + ) + .getOrElse( + () => acc.upsertAt( + eq, + key, + value, + ), + ), + ); /// Intersect the key/value of two [Map] using `combine` where the key is the same. - Map Function(Map map) intersection(Eq eq, Magma combine) => - (Map map) => map.foldLeftWithKey>(Order.allEqual())( - {}, - (acc, key, value) => lookup(key).match( - () => acc, - (v) => acc.upsertAt(eq)(key, combine(v, value)), - ), - ); + Map intersection( + Eq eq, + V Function(V x, V y) combine, + Map map, + ) => + map.foldLeftWithKey>( + Order.allEqual(), + {}, + (acc, key, value) => lookup(key).match( + () => acc, + (v) => acc.upsertAt( + eq, + key, + combine(v, value), + ), + ), + ); /// Test whether or not the given `map` contains all of the keys and values contained in this [Map]. - bool Function(Map) Function(Eq) isSubmap(Eq eqK) => - (Eq eqV) => (Map map) => foldLeftWithKey(Order.allEqual())( - true, - (b, k, v) => - b && - map.entries.any( - (e) => eqK.eqv(e.key, k) && eqV.eqv(e.value, v), - ), - ); - - /// Collect all the entries in this [Map] into an [Iterable] using `fun` with the values ordered using `order`. - Iterable Function(A Function(K key, V value)) collect(Order order) => - (A Function(K key, V value) fun) => - toIterable(order).map((item) => fun(item.key, item.value)); + bool isSubmap( + Eq eqK, + Eq eqV, + Map map, + ) => + foldLeftWithKey( + Order.allEqual(), + true, + (b, k, v) => + b && + map.entries.any( + (e) => eqK.eqv(e.key, k) && eqV.eqv(e.value, v), + ), + ); + + /// Collect all the entries in this [Map] into an [Iterable] using `compose`, + /// with the values ordered using `order`. + Iterable collect(Order order, A Function(K key, V value) compose) => + toSortedList(order).map( + (item) => compose( + item.key, + item.value, + ), + ); /// Remove from this [Map] all the elements that have **key** contained in the given `map`. - Map Function(Map map) difference(Eq eq) => - (Map map) => filterWithKey( - (key, value) => !map.keys.any((element) => eq.eqv(element, key)), - ); + Map difference(Eq eq, Map map) => filterWithKey( + (key, value) => !map.keys.any( + (element) => eq.eqv(element, key), + ), + ); + + /// Get a sorted [List] of the key/value pairs contained in this [Map] + /// based on `order` on keys. + List> toSortedList(Order order) => entries.sortWith( + (map) => map.key, + order, + ); } -extension FpdartOnOptionMutableMap on Option> { +extension FpdartOnOptionMap on Option> { /// Return an [Option] that conditionally accesses map keys, only if they match the /// given type. + /// /// Useful for accessing nested JSON. /// - /// ``` - /// expect( - /// { 'test': 123 }.extract('test'), - /// Option.of(123), - /// ); - /// expect( - /// { 'test': 'string' }.extract('test'), - /// Option.none(), - /// ); + /// ```dart + /// { 'test': 123 }.extract('test'); /// `Some(123)` + /// { 'test': 'string' }.extract('test'); /// `None()` /// ``` Option extract(K key) => flatMap((map) => map.extract(key)); /// Return an [Option] that conditionally accesses map keys, if they contain a map /// with the same key type. + /// /// Useful for accessing nested JSON. /// - /// ``` - /// expect( - /// { 'test': { 'foo': 'bar' } }.extractMap('test'), - /// Option.of({ 'foo': 'bar' }), - /// ); - /// expect( - /// { 'test': 'string' }.extractMap('test'), - /// Option.none(), - /// ); + /// ```dart + /// { 'test': { 'foo': 'bar' } }.extractMap('test'); /// `Some({ 'foo': 'bar' })` + /// { 'test': 'string' }.extractMap('test'); /// `None()` /// ``` Option> extractMap(K key) => extract>(key); } diff --git a/packages/fpdart/lib/src/typedef.dart b/packages/fpdart/lib/src/typedef.dart index 6c1895ce..0d8225fa 100644 --- a/packages/fpdart/lib/src/typedef.dart +++ b/packages/fpdart/lib/src/typedef.dart @@ -2,4 +2,3 @@ import 'typeclass/hkt.dart'; typedef Endo = A Function(A a); typedef Separated = (HKT, HKT); -typedef Magma = T Function(T x, T y); diff --git a/packages/fpdart/test/src/extension/iterable_extension_test.dart b/packages/fpdart/test/src/extension/iterable_extension_test.dart index 6010fe27..5b94e806 100644 --- a/packages/fpdart/test/src/extension/iterable_extension_test.dart +++ b/packages/fpdart/test/src/extension/iterable_extension_test.dart @@ -43,6 +43,13 @@ void main() { expect(eq(ap, [4, 5, 6]), true); }); + test('filterWithIndex', () { + final list1 = [0, 1, 2, 3, 4, 5, 6]; + final ap = list1.filterWithIndex((t, index) => t > 3 && index < 6); + + expect(eq(ap, [4, 5]), true); + }); + test('concat', () { final list1 = [1, 2, 3, 4, 5, 6]; final ap = list1.concat([7, 8]); diff --git a/packages/fpdart/test/src/extension/map_extension_test.dart b/packages/fpdart/test/src/extension/map_extension_test.dart index f678a9a3..09c691d7 100644 --- a/packages/fpdart/test/src/extension/map_extension_test.dart +++ b/packages/fpdart/test/src/extension/map_extension_test.dart @@ -126,25 +126,24 @@ void main() { test('member', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.member('b'); - final ap2 = map.member('e'); - expect(ap, true); - expect(ap2, false); + expect(map.containsKey('b'), true); + expect(map.containsKey('e'), false); }); test('elem', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.elem(1); - final ap2 = map.elem(0); - expect(ap, true); - expect(ap2, false); + expect(map.containsValue(1), true); + expect(map.containsValue(0), false); }); group('modifyAt', () { test('Some', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = - map.modifyAt(Eq.instance((a1, a2) => a1 == a2))('b', (v) => v + 2); + final ap = map.modifyAt( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'b', + ); ap.matchTestSome((t) => t.lookup('b').matchTestSome((t) { expect(t, 4); })); @@ -152,8 +151,11 @@ void main() { test('None', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = - map.modifyAt(Eq.instance((a1, a2) => a1 == a2))('e', (v) => v + 2); + final ap = map.modifyAt( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'e', + ); expect(ap, isA()); }); }); @@ -161,8 +163,11 @@ void main() { group('modifyAtIfPresent', () { test('found', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.modifyAtIfPresent(Eq.instance((a1, a2) => a1 == a2))( - 'b', (v) => v + 2); + final ap = map.modifyAtIfPresent( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'b', + ); ap.lookup('b').matchTestSome((t) { expect(t, 4); }); @@ -170,8 +175,11 @@ void main() { test('not found', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.modifyAtIfPresent(Eq.instance((a1, a2) => a1 == a2))( - 'e', (v) => v + 2); + final ap = map.modifyAtIfPresent( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'e', + ); ap.lookup('b').matchTestSome((t) { expect(t, 2); }); @@ -181,7 +189,7 @@ void main() { group('updateAt', () { test('Some', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.updateAt(Eq.instance((a1, a2) => a1 == a2))('b', 10); + final ap = map.updateAt(Eq.instance((a1, a2) => a1 == a2), 'b', 10); ap.matchTestSome((t) => t.lookup('b').matchTestSome((t) { expect(t, 10); })); @@ -189,7 +197,7 @@ void main() { test('None', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.updateAt(Eq.instance((a1, a2) => a1 == a2))('e', 10); + final ap = map.updateAt(Eq.instance((a1, a2) => a1 == a2), 'e', 10); expect(ap, isA()); }); }); From 1f7f7671011b78f0cdbadb92abe825252c57d748 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Mon, 22 May 2023 20:12:25 +0200 Subject: [PATCH 44/71] updating test map extension --- packages/fpdart/CHANGELOG.md | 4 + packages/fpdart/example/src/map/overview.dart | 7 +- .../lib/src/extension/map_extension.dart | 68 ++++---- .../src/extension/map_extension_test.dart | 158 +++++++++++++----- 4 files changed, 166 insertions(+), 71 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 9649a1cb..f9966a90 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -41,6 +41,10 @@ - `concatMapWithIndex` (use `flatMapWithIndex` instead) - Refactoring of `Map` extension methods - Improved performance + - Added the following methods + - `lookupEq` + - `lookupWithKeyEq` + - `containsKeyEq` - Removed the following methods ⚠️ - `member` (use `containsKey` instead) - `elem` (use `containsValue` instead) diff --git a/packages/fpdart/example/src/map/overview.dart b/packages/fpdart/example/src/map/overview.dart index 88b7a10d..27738037 100644 --- a/packages/fpdart/example/src/map/overview.dart +++ b/packages/fpdart/example/src/map/overview.dart @@ -9,8 +9,11 @@ void main() { /// /// The first date `d1` will be overwritten by the second date `d2`, /// since the year is the same. - final map = - {}.upsertAt(dateEqYear)(d1, 1).upsertAt(dateEqYear)(d2, 2); + final map = {}.upsertAt(dateEqYear, d1, 1).upsertAt( + dateEqYear, + d2, + 2, + ); print(map); // {2001-01-02 00:00:00.000: 2} } diff --git a/packages/fpdart/lib/src/extension/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart index 0f7b18be..438f3790 100644 --- a/packages/fpdart/lib/src/extension/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -66,15 +66,33 @@ extension FpdartOnMap on Map { ), ); + /// Returns this [Map] if it contains a key based on `eq`. + Option> containsKeyEq(Eq eq, K key) => Option.tryCatch( + () => entries.firstWhere((entry) => eq.eqv(entry.key, key)), + ).map((_) => {...this}); + /// Get the value at given `key` if present, otherwise return [None]. Option lookup(K key) => Option.fromNullable(this[key]); + /// Get the value at given `key` if present using `eq`, otherwise return [None]. + Option lookupEq(Eq eq, K key) => Option.tryCatch( + () => entries.firstWhere((entry) => eq.eqv(entry.key, key)).value, + ); + /// Get the value and key at given `key` if present, otherwise return [None]. Option<(K, V)> lookupWithKey(K key) { final value = this[key]; return value != null ? some((key, value)) : const None(); } + /// Get the value and key at given `key` if present using `eq`, otherwise return [None]. + Option<(K, V)> lookupWithKeyEq(Eq eq, K key) => Option.tryCatch( + () { + final entry = entries.firstWhere((entry) => eq.eqv(entry.key, key)); + return (entry.key, entry.value); + }, + ); + /// Return an [Option] that conditionally accesses map keys, only if they match the /// given type. /// @@ -108,22 +126,15 @@ extension FpdartOnMap on Map { Eq eq, V Function(V value) update, K key, - ) { - if (containsKey(key)) { - return some( - map( - (k, v) => eq.eqv(k, key) - ? MapEntry( - key, - update(v), - ) - : MapEntry(k, v), + ) => + containsKeyEq(eq, key).map( + (newMap) => newMap.map( + (k, v) => MapEntry( + key, + eq.eqv(k, key) ? update(v) : v, + ), ), ); - } - - return const None(); - } /// If the given `key` is present in the [Map], then modify its value /// using `update` and return a the new [Map]. @@ -139,22 +150,15 @@ extension FpdartOnMap on Map { /// If the given `key` is present in the [Map], then update its value to `value`. /// /// Otherwise, return [None]. - Option> updateAt(Eq eq, K key, V value) { - if (containsKey(key)) { - return some( - map( - (k, v) => eq.eqv(k, key) - ? MapEntry( - key, - value, - ) - : MapEntry(k, v), + Option> updateAt(Eq eq, K key, V value) => + containsKeyEq(eq, key).map( + (newMap) => newMap.map( + (k, v) => MapEntry( + key, + eq.eqv(k, key) ? value : v, + ), ), ); - } - - return const None(); - } /// If the given `key` is present in the [Map], then update its value to `value`. /// Otherwise, return a copy of the original unmodified [Map]. @@ -173,12 +177,14 @@ extension FpdartOnMap on Map { /// Insert or replace a key/value pair in a [Map]. Map upsertAt(Eq eq, K key, V value) => - modifyAtIfPresent(eq, (_) => value, key); + modifyAt(eq, (_) => value, key).getOrElse( + () => {...this, key: value}, + ); /// Delete a `key` and value from a this [Map], returning the deleted value as well as the updated [Map]. /// /// If `key` is not present, then return [None]. - Option<(V, Map)> pop(Eq eq, K key) => lookup(key).map( + Option<(V, Map)> pop(Eq eq, K key) => lookupEq(eq, key).map( (v) => (v, deleteAt(eq, key)), ); @@ -343,7 +349,7 @@ extension FpdartOnMap on Map { map.foldLeftWithKey>( Order.allEqual(), {}, - (acc, key, value) => lookup(key).match( + (acc, key, value) => lookupEq(eq, key).match( () => acc, (v) => acc.upsertAt( eq, diff --git a/packages/fpdart/test/src/extension/map_extension_test.dart b/packages/fpdart/test/src/extension/map_extension_test.dart index 09c691d7..4797b64c 100644 --- a/packages/fpdart/test/src/extension/map_extension_test.dart +++ b/packages/fpdart/test/src/extension/map_extension_test.dart @@ -57,6 +57,82 @@ void main() { }); }); + group('lookupEq', () { + test('Some', () { + final map = { + DateTime(2000, 1, 1): 1, + DateTime(2001, 1, 1): 2, + }; + final ap = map.lookupEq(dateEqYear, DateTime(2000, 10, 10)); + ap.matchTestSome((t) { + expect(t, 1); + }); + }); + + test('None', () { + final map = { + DateTime(2000, 1, 1): 1, + DateTime(2001, 1, 1): 2, + }; + final ap = map.lookupEq(dateEqYear, DateTime(2002, 1, 1)); + expect(ap, isA()); + }); + }); + + group('lookupWithKeyEq', () { + test('Some', () { + final map = { + DateTime(2000, 1, 1): 1, + DateTime(2001, 1, 1): 2, + }; + final ap = map.lookupWithKeyEq(dateEqYear, DateTime(2000, 10, 10)); + ap.matchTestSome((t) { + expect(t, (DateTime(2000, 1, 1), 1)); + }); + }); + + test('None', () { + final map = { + DateTime(2000, 1, 1): 1, + DateTime(2001, 1, 1): 2, + }; + final ap = map.lookupWithKeyEq(dateEqYear, DateTime(2002, 1, 1)); + expect(ap, isA()); + }); + }); + + group('containsKeyEq', () { + test('Some', () { + final map = { + DateTime(2000, 1, 1): 1, + DateTime(2001, 1, 1): 2, + }; + final ap = map.containsKeyEq(dateEqYear, DateTime(2000, 10, 10)); + ap.matchTestSome((t) { + t + .lookupWithKeyEq(dateEqYear, DateTime(2000, 1, 1)) + .matchTestSome((v) { + expect(v, (DateTime(2000, 1, 1), 1)); + }); + + t + .lookupWithKeyEq(dateEqYear, DateTime(2001, 1, 1)) + .matchTestSome((v) { + expect(v, (DateTime(2001, 1, 1), 2)); + }); + }); + }); + + test('None', () { + final map = { + DateTime(2000, 1, 1): 1, + DateTime(2001, 1, 1): 2, + }; + final ap = map.containsKeyEq(dateEqYear, DateTime(2002, 1, 1)); + expect(ap, isA()); + }); + }); + group('lookupWithKey', () { test('Some', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; @@ -206,7 +282,7 @@ void main() { test('found', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final ap = - map.updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2))('b', 10); + map.updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2), 'b', 10); ap.lookup('b').matchTestSome((t) { expect(t, 10); }); @@ -215,7 +291,7 @@ void main() { test('not found', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final ap = - map.updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2))('e', 10); + map.updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2), 'e', 10); ap.lookup('b').matchTestSome((t) { expect(t, 2); }); @@ -225,7 +301,7 @@ void main() { test('deleteAt', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; expect(map.lookup('b'), isA()); - final ap = map.deleteAt(Eq.instance((a1, a2) => a1 == a2))('b'); + final ap = map.deleteAt(Eq.instance((a1, a2) => a1 == a2), 'b'); expect(map.lookup('b'), isA()); expect(ap.lookup('b'), isA()); }); @@ -234,8 +310,10 @@ void main() { test('insert', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; expect(map.lookup('e'), isA()); - final ap = map.upsertAt(Eq.instance((a1, a2) => a1 == a2))('e', 10); + + final ap = map.upsertAt(Eq.instance((a1, a2) => a1 == a2), 'e', 10); expect(map.lookup('e'), isA()); + ap.lookup('e').matchTestSome((t) { expect(t, 10); }); @@ -246,7 +324,7 @@ void main() { map.lookup('b').matchTestSome((t) { expect(t, 2); }); - final ap = map.upsertAt(Eq.instance((a1, a2) => a1 == a2))('b', 10); + final ap = map.upsertAt(Eq.instance((a1, a2) => a1 == a2), 'b', 10); map.lookup('b').matchTestSome((t) { expect(t, 2); }); @@ -259,12 +337,13 @@ void main() { final d1 = DateTime(2001, 1, 1); final d2 = DateTime(2001, 1, 2); final map = {} - .upsertAt(dateEqYear)(d1, 1) - .upsertAt(dateEqYear)(d2, 2); + .upsertAt(dateEqYear, d1, 1) + .upsertAt(dateEqYear, d2, 2); - expect(map.lookup(d1), isA()); - expect(map.lookup(d2), isA()); - map.lookup(d2).matchTestSome((t) { + map.lookupEq(dateEqYear, d1).matchTestSome((t) { + expect(t, 2); + }); + map.lookupEq(dateEqYear, d2).matchTestSome((t) { expect(t, 2); }); }); @@ -273,7 +352,7 @@ void main() { group('pop', () { test('Some', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.pop(Eq.instance((a1, a2) => a1 == a2))('b'); + final ap = map.pop(Eq.instance((a1, a2) => a1 == a2), 'b'); expect(map.lookup('b'), isA()); ap.matchTestSome((t) { expect(t.$1, 2); @@ -283,7 +362,7 @@ void main() { test('None', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.pop(Eq.instance((a1, a2) => a1 == a2))('e'); + final ap = map.pop(Eq.instance((a1, a2) => a1 == a2), 'e'); expect(ap, isA()); }); }); @@ -291,56 +370,56 @@ void main() { test('foldLeft', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final ap = - map.foldLeft(Order.allEqual())('', (acc, a) => '$acc$a'); + map.foldLeft(Order.allEqual(), '', (acc, a) => '$acc$a'); expect(ap, '1234'); }); test('foldLeftWithIndex', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldLeftWithIndex(Order.allEqual())( - '', (acc, a, i) => '$acc$a$i'); + final ap = map.foldLeftWithIndex( + Order.allEqual(), '', (acc, a, i) => '$acc$a$i'); expect(ap, '10213243'); }); test('foldLeftWithKey', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldLeftWithKey(Order.allEqual())( - '', (acc, k, v) => '$acc$k$v'); + final ap = map.foldLeftWithKey( + Order.allEqual(), '', (acc, k, v) => '$acc$k$v'); expect(ap, 'a1b2c3d4'); }); test('foldLeftWithKeyAndIndex', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldLeftWithKeyAndIndex(Order.allEqual())( - '', (acc, k, v, i) => '$acc$k$v$i'); + final ap = map.foldLeftWithKeyAndIndex( + Order.allEqual(), '', (acc, k, v, i) => '$acc$k$v$i'); expect(ap, 'a10b21c32d43'); }); test('foldRight', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final ap = - map.foldRight(Order.allEqual())('', (a, acc) => '$acc$a'); + map.foldRight(Order.allEqual(), '', (a, acc) => '$acc$a'); expect(ap, '4321'); }); test('foldRightWithIndex', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldRightWithIndex(Order.allEqual())( - '', (a, acc, i) => '$acc$a$i'); + final ap = map.foldRightWithIndex( + Order.allEqual(), '', (a, acc, i) => '$acc$a$i'); expect(ap, '40312213'); }); test('foldRightWithKey', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldRightWithKey(Order.allEqual())( - '', (k, v, acc) => '$acc$k$v'); + final ap = map.foldRightWithKey( + Order.allEqual(), '', (k, v, acc) => '$acc$k$v'); expect(ap, 'd4c3b2a1'); }); test('foldRightWithKeyAndIndex', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldRightWithKeyAndIndex(Order.allEqual())( - '', (k, v, acc, i) => '$acc$k$v$i'); + final ap = map.foldRightWithKeyAndIndex( + Order.allEqual(), '', (k, v, acc, i) => '$acc$k$v$i'); expect(ap, 'd40c31b22a13'); }); @@ -350,9 +429,9 @@ void main() { expect(ap, 4); }); - test('toIterable', () { + test('toSortedList', () { final map = {'c': 3, 'd': 4, 'a': 1, 'b': 2}; - final ap = map.toIterable(Order.from((a1, a2) => a1.compareTo(a2))); + final ap = map.toSortedList(Order.from((a1, a2) => a1.compareTo(a2))); expect(ap.elementAt(0).value, 1); expect(ap.elementAt(1).value, 2); expect(ap.elementAt(2).value, 3); @@ -367,7 +446,7 @@ void main() { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final map1 = {'c': 20, 'e': 10}; final ap = - map.union(Eq.instance((a1, a2) => a1 == a2), (x, y) => x + y)(map1); + map.union(Eq.instance((a1, a2) => a1 == a2), (x, y) => x + y, map1); expect(ap['a'], 1); expect(ap['b'], 2); expect(ap['c'], 23); @@ -379,7 +458,7 @@ void main() { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final map1 = {'c': 20, 'e': 10}; final ap = map.intersection( - Eq.instance((a1, a2) => a1 == a2), (x, y) => x + y)(map1); + Eq.instance((a1, a2) => a1 == a2), (x, y) => x + y, map1); expect(ap['a'], null); expect(ap['b'], null); expect(ap['c'], 23); @@ -391,31 +470,34 @@ void main() { test('true', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final map1 = {'a': 1, 'c': 3}; - final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2))( - Eq.instance((a1, a2) => a1 == a2))(map); + final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2), + Eq.instance((a1, a2) => a1 == a2), map); expect(ap, true); }); test('false (value)', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final map1 = {'a': 1, 'c': 2}; - final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2))( - Eq.instance((a1, a2) => a1 == a2))(map); + final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2), + Eq.instance((a1, a2) => a1 == a2), map); expect(ap, false); }); test('false (key)', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final map1 = {'a': 1, 'd': 3}; - final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2))( - Eq.instance((a1, a2) => a1 == a2))(map); + final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2), + Eq.instance((a1, a2) => a1 == a2), map); expect(ap, false); }); }); test('collect', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.collect(Order.from((a1, a2) => a1.compareTo(a2)))( + final ap = map.collect( + Order.from( + (a1, a2) => a1.compareTo(a2), + ), (k, v) => '$k$v'); expect(ap.elementAt(0), 'a1'); expect(ap.elementAt(1), 'b2'); @@ -426,7 +508,7 @@ void main() { test('difference', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; final map1 = {'a': 1, 'c': 3}; - final ap = map.difference(Eq.instance((a1, a2) => a1 == a2))(map1); + final ap = map.difference(Eq.instance((a1, a2) => a1 == a2), map1); expect(ap['a'], null); expect(ap['b'], 2); expect(ap['c'], null); From 45e11150878d1db6953e5a7fee7d43ece95c89d5 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Tue, 23 May 2023 12:06:06 +0200 Subject: [PATCH 45/71] fix tests --- .../fpdart/lib/src/extension/map_extension.dart | 14 +++++++------- .../test/src/extension/map_extension_test.dart | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/fpdart/lib/src/extension/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart index 438f3790..f7436205 100644 --- a/packages/fpdart/lib/src/extension/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -68,7 +68,7 @@ extension FpdartOnMap on Map { /// Returns this [Map] if it contains a key based on `eq`. Option> containsKeyEq(Eq eq, K key) => Option.tryCatch( - () => entries.firstWhere((entry) => eq.eqv(entry.key, key)), + () => keys.firstWhere((entryKey) => eq.eqv(entryKey, key)), ).map((_) => {...this}); /// Get the value at given `key` if present, otherwise return [None]. @@ -129,9 +129,9 @@ extension FpdartOnMap on Map { ) => containsKeyEq(eq, key).map( (newMap) => newMap.map( - (k, v) => MapEntry( - key, - eq.eqv(k, key) ? update(v) : v, + (entryKey, oldValue) => MapEntry( + entryKey, + eq.eqv(entryKey, key) ? update(oldValue) : oldValue, ), ), ); @@ -153,9 +153,9 @@ extension FpdartOnMap on Map { Option> updateAt(Eq eq, K key, V value) => containsKeyEq(eq, key).map( (newMap) => newMap.map( - (k, v) => MapEntry( - key, - eq.eqv(k, key) ? value : v, + (entryKey, oldValue) => MapEntry( + entryKey, + eq.eqv(entryKey, key) ? value : oldValue, ), ), ); diff --git a/packages/fpdart/test/src/extension/map_extension_test.dart b/packages/fpdart/test/src/extension/map_extension_test.dart index 4797b64c..011fe2a4 100644 --- a/packages/fpdart/test/src/extension/map_extension_test.dart +++ b/packages/fpdart/test/src/extension/map_extension_test.dart @@ -265,7 +265,7 @@ void main() { group('updateAt', () { test('Some', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.updateAt(Eq.instance((a1, a2) => a1 == a2), 'b', 10); + final ap = map.updateAt(Eq.eqString(), 'b', 10); ap.matchTestSome((t) => t.lookup('b').matchTestSome((t) { expect(t, 10); })); @@ -273,7 +273,7 @@ void main() { test('None', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.updateAt(Eq.instance((a1, a2) => a1 == a2), 'e', 10); + final ap = map.updateAt(Eq.eqString(), 'e', 10); expect(ap, isA()); }); }); @@ -321,14 +321,14 @@ void main() { test('update', () { final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - map.lookup('b').matchTestSome((t) { + map.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { expect(t, 2); }); - final ap = map.upsertAt(Eq.instance((a1, a2) => a1 == a2), 'b', 10); - map.lookup('b').matchTestSome((t) { + final ap = map.upsertAt(Eq.eqString(), 'b', 10); + map.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { expect(t, 2); }); - ap.lookup('b').matchTestSome((t) { + ap.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { expect(t, 10); }); }); From 061f046185ab0e450ea93ac092ae95ef02e8cc52 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 24 May 2023 13:00:20 +0200 Subject: [PATCH 46/71] test map extension immutability --- packages/fpdart/CHANGELOG.md | 1 - .../lib/src/extension/map_extension.dart | 64 +- packages/fpdart/pubspec.yaml | 1 + .../src/extension/map_extension_test.dart | 688 ++++++++++-------- .../test/src/utils/collection_utils.dart | 18 + packages/fpdart/test/src/utils/utils.dart | 1 + 6 files changed, 446 insertions(+), 327 deletions(-) create mode 100644 packages/fpdart/test/src/utils/collection_utils.dart diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index f9966a90..bcb7c0a5 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -44,7 +44,6 @@ - Added the following methods - `lookupEq` - `lookupWithKeyEq` - - `containsKeyEq` - Removed the following methods ⚠️ - `member` (use `containsKey` instead) - `elem` (use `containsValue` instead) diff --git a/packages/fpdart/lib/src/extension/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart index f7436205..ba5627a4 100644 --- a/packages/fpdart/lib/src/extension/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -6,7 +6,7 @@ import 'option_extension.dart'; /// Functional programming functions on a mutable dart [Map] using `fpdart`. extension FpdartOnMap on Map { - /// Return number of keys in the [Map] (`keys.length`). + /// Return number of elements in the [Map] (`keys.length`). int get size => keys.length; /// Convert each **value** of the [Map] using @@ -29,14 +29,16 @@ extension FpdartOnMap on Map { ), ); - /// Returns the list of those elements of the [Map] whose **value** satisfies `test`. + /// Returns a new [Map] containing all the elements of this [Map] + /// where the **value** satisfies `test`. Map filter(bool Function(V value) test) => Map.fromEntries( entries.where( (entry) => test(entry.value), ), ); - /// Returns the list of those elements of the [Map] whose **value** satisfies `test`. + /// Returns a new [Map] containing all the elements of this [Map] + /// where the **value** satisfies `test`. Map filterWithIndex(bool Function(V value, int index) test) => Map.fromEntries( entries.filterWithIndex( @@ -44,7 +46,8 @@ extension FpdartOnMap on Map { ), ); - /// Returns the list of those elements of the [Map] whose key/value satisfies `test`. + /// Returns a new [Map] containing all the elements of this [Map] + /// where **key/value** satisfies `test`. Map filterWithKey(bool Function(K key, V value) test) => Map.fromEntries( entries.where( @@ -52,7 +55,8 @@ extension FpdartOnMap on Map { ), ); - /// Returns the list of those elements of the [Map] whose key/value satisfies `test`. + /// Returns a new [Map] containing all the elements of this [Map] + /// where **key/value** satisfies `test`. Map filterWithKeyAndIndex( bool Function(K key, V value, int index) test, ) => @@ -66,25 +70,20 @@ extension FpdartOnMap on Map { ), ); - /// Returns this [Map] if it contains a key based on `eq`. - Option> containsKeyEq(Eq eq, K key) => Option.tryCatch( - () => keys.firstWhere((entryKey) => eq.eqv(entryKey, key)), - ).map((_) => {...this}); - /// Get the value at given `key` if present, otherwise return [None]. Option lookup(K key) => Option.fromNullable(this[key]); - /// Get the value at given `key` if present using `eq`, otherwise return [None]. - Option lookupEq(Eq eq, K key) => Option.tryCatch( - () => entries.firstWhere((entry) => eq.eqv(entry.key, key)).value, - ); - /// Get the value and key at given `key` if present, otherwise return [None]. Option<(K, V)> lookupWithKey(K key) { final value = this[key]; return value != null ? some((key, value)) : const None(); } + /// Get the value at given `key` if present using `eq`, otherwise return [None]. + Option lookupEq(Eq eq, K key) => Option.tryCatch( + () => entries.firstWhere((entry) => eq.eqv(entry.key, key)).value, + ); + /// Get the value and key at given `key` if present using `eq`, otherwise return [None]. Option<(K, V)> lookupWithKeyEq(Eq eq, K key) => Option.tryCatch( () { @@ -127,8 +126,10 @@ extension FpdartOnMap on Map { V Function(V value) update, K key, ) => - containsKeyEq(eq, key).map( - (newMap) => newMap.map( + Option.tryCatch( + () => keys.firstWhere((entryKey) => eq.eqv(entryKey, key)), + ).map( + (_) => map( (entryKey, oldValue) => MapEntry( entryKey, eq.eqv(entryKey, key) ? update(oldValue) : oldValue, @@ -150,9 +151,10 @@ extension FpdartOnMap on Map { /// If the given `key` is present in the [Map], then update its value to `value`. /// /// Otherwise, return [None]. - Option> updateAt(Eq eq, K key, V value) => - containsKeyEq(eq, key).map( - (newMap) => newMap.map( + Option> updateAt(Eq eq, K key, V value) => Option.tryCatch( + () => keys.firstWhere((entryKey) => eq.eqv(entryKey, key)), + ).map( + (_) => map( (entryKey, oldValue) => MapEntry( entryKey, eq.eqv(entryKey, key) ? value : oldValue, @@ -172,6 +174,8 @@ extension FpdartOnMap on Map { ); /// Delete entry at given `key` if present in the [Map] and return updated [Map]. + /// + /// See also `pop`. Map deleteAt(Eq eq, K key) => filterWithKey((k, v) => !eq.eqv(k, key)); @@ -184,6 +188,8 @@ extension FpdartOnMap on Map { /// Delete a `key` and value from a this [Map], returning the deleted value as well as the updated [Map]. /// /// If `key` is not present, then return [None]. + /// + /// See also `deleteAt`. Option<(V, Map)> pop(Eq eq, K key) => lookupEq(eq, key).map( (v) => (v, deleteAt(eq, key)), ); @@ -359,6 +365,13 @@ extension FpdartOnMap on Map { ), ); + /// Remove from this [Map] all the elements that have **key** contained in the given `map`. + Map difference(Eq eq, Map map) => filterWithKey( + (key, value) => !map.keys.any( + (element) => eq.eqv(element, key), + ), + ); + /// Test whether or not the given `map` contains all of the keys and values contained in this [Map]. bool isSubmap( Eq eqK, @@ -377,6 +390,8 @@ extension FpdartOnMap on Map { /// Collect all the entries in this [Map] into an [Iterable] using `compose`, /// with the values ordered using `order`. + /// + /// See also `toSortedList`. Iterable collect(Order order, A Function(K key, V value) compose) => toSortedList(order).map( (item) => compose( @@ -385,15 +400,10 @@ extension FpdartOnMap on Map { ), ); - /// Remove from this [Map] all the elements that have **key** contained in the given `map`. - Map difference(Eq eq, Map map) => filterWithKey( - (key, value) => !map.keys.any( - (element) => eq.eqv(element, key), - ), - ); - /// Get a sorted [List] of the key/value pairs contained in this [Map] /// based on `order` on keys. + /// + /// See also `collect`. List> toSortedList(Order order) => entries.sortWith( (map) => map.key, order, diff --git a/packages/fpdart/pubspec.yaml b/packages/fpdart/pubspec.yaml index 464669a8..e91a706e 100644 --- a/packages/fpdart/pubspec.yaml +++ b/packages/fpdart/pubspec.yaml @@ -16,6 +16,7 @@ dev_dependencies: lints: ^2.0.1 test: ^1.23.1 glados: ^1.1.6 + collection: ^1.17.2 screenshots: - description: "Basic usage of fpdart Option, Either, TaskEither types." diff --git a/packages/fpdart/test/src/extension/map_extension_test.dart b/packages/fpdart/test/src/extension/map_extension_test.dart index 011fe2a4..306114ba 100644 --- a/packages/fpdart/test/src/extension/map_extension_test.dart +++ b/packages/fpdart/test/src/extension/map_extension_test.dart @@ -5,514 +5,604 @@ import '../utils/utils.dart'; void main() { group('FpdartOnMutableMap', () { test('mapValue', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.mapValue((value) => '${value * 2}'); - expect(ap, {'a': '2', 'b': '4', 'c': '6', 'd': '8'}); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.mapValue((value) => '${value * 2}'), + {'a': '2', 'b': '4', 'c': '6', 'd': '8'}, + ); + }); }); test('mapWithIndex', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.mapWithIndex((value, index) => '${value + index}'); - expect(ap, {'a': '1', 'b': '3', 'c': '5', 'd': '7'}); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.mapWithIndex((value, index) => '${value + index}'), + {'a': '1', 'b': '3', 'c': '5', 'd': '7'}, + ); + }); }); test('filter', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.filter((t) => t > 2); - expect(ap, {'c': 3, 'd': 4}); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.filter((t) => t > 2), + {'c': 3, 'd': 4}, + ); + }); }); test('filterWithIndex', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.filterWithIndex((t, i) => t > 2 && i != 3); - expect(ap, {'c': 3}); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.filterWithIndex((t, i) => t > 2 && i != 3), + {'c': 3}, + ); + }); }); test('filterWithKey', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.filterWithKey((k, v) => v > 2 && k != 'd'); - expect(ap, {'c': 3}); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.filterWithKey((k, v) => v > 2 && k != 'd'), + {'c': 3}, + ); + }); }); test('filterWithKeyAndIndex', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = - map.filterWithKeyAndIndex((k, v, i) => v > 1 && i != 1 && k != 'd'); - expect(ap, {'c': 3}); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.filterWithKeyAndIndex((k, v, i) => v > 1 && i != 1 && k != 'd'), + {'c': 3}, + ); + }); }); group('lookup', () { test('Some', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.lookup('b'); - ap.matchTestSome((t) { - expect(t, 2); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value.lookup('b').matchTestSome((t) { + expect(t, 2); + }); }); }); test('None', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.lookup('e'); - expect(ap, isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect(value.lookup('e'), isA()); + }); }); }); group('lookupEq', () { test('Some', () { - final map = { + testImmutableMap({ DateTime(2000, 1, 1): 1, DateTime(2001, 1, 1): 2, - }; - final ap = map.lookupEq(dateEqYear, DateTime(2000, 10, 10)); - ap.matchTestSome((t) { - expect(t, 1); + }, (value) { + value.lookupEq(dateEqYear, DateTime(2000, 10, 10)).matchTestSome((t) { + expect(t, 1); + }); }); }); test('None', () { - final map = { + testImmutableMap({ DateTime(2000, 1, 1): 1, DateTime(2001, 1, 1): 2, - }; - final ap = map.lookupEq(dateEqYear, DateTime(2002, 1, 1)); - expect(ap, isA()); - }); - }); - - group('lookupWithKeyEq', () { - test('Some', () { - final map = { - DateTime(2000, 1, 1): 1, - DateTime(2001, 1, 1): 2, - }; - final ap = map.lookupWithKeyEq(dateEqYear, DateTime(2000, 10, 10)); - ap.matchTestSome((t) { - expect(t, (DateTime(2000, 1, 1), 1)); + }, (value) { + expect(value.lookupEq(dateEqYear, DateTime(2002, 1, 1)), isA()); }); }); - - test('None', () { - final map = { - DateTime(2000, 1, 1): 1, - DateTime(2001, 1, 1): 2, - }; - final ap = map.lookupWithKeyEq(dateEqYear, DateTime(2002, 1, 1)); - expect(ap, isA()); - }); }); - group('containsKeyEq', () { + group('lookupWithKeyEq', () { test('Some', () { - final map = { + testImmutableMap({ DateTime(2000, 1, 1): 1, DateTime(2001, 1, 1): 2, - }; - final ap = map.containsKeyEq(dateEqYear, DateTime(2000, 10, 10)); - ap.matchTestSome((t) { - t - .lookupWithKeyEq(dateEqYear, DateTime(2000, 1, 1)) - .matchTestSome((v) { - expect(v, (DateTime(2000, 1, 1), 1)); - }); - - t - .lookupWithKeyEq(dateEqYear, DateTime(2001, 1, 1)) - .matchTestSome((v) { - expect(v, (DateTime(2001, 1, 1), 2)); + }, (value) { + value + .lookupWithKeyEq( + dateEqYear, + DateTime(2000, 10, 10), + ) + .matchTestSome((t) { + expect(t, (DateTime(2000, 1, 1), 1)); }); }); }); test('None', () { - final map = { + testImmutableMap({ DateTime(2000, 1, 1): 1, DateTime(2001, 1, 1): 2, - }; - final ap = map.containsKeyEq(dateEqYear, DateTime(2002, 1, 1)); - expect(ap, isA()); + }, (value) { + expect( + value.lookupWithKeyEq(dateEqYear, DateTime(2002, 1, 1)), + isA(), + ); + }); }); }); group('lookupWithKey', () { test('Some', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.lookupWithKey('b'); - ap.matchTestSome((t) { - expect(t.$1, 'b'); - expect(t.$2, 2); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value.lookupWithKey('b').matchTestSome((t) { + expect(t.$1, 'b'); + expect(t.$2, 2); + }); }); }); test('None', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.lookupWithKey('e'); - expect(ap, isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect(value.lookupWithKey('e'), isA()); + }); }); }); group('extract', () { test('valid', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.extract('b'); - expect(ap, Option.of(2)); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value.extract('b').matchTestSome((t) { + expect(t, 2); + }); + }); }); test('wrong type', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.extract('b'); - expect(ap, isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect(value.extract('b'), isA()); + }); }); }); group('extractMap', () { test('no map', () { - final map = {'a': 1}; - final ap = map.extractMap('a'); - expect(ap, isA()); + testImmutableMap({'a': 1}, (value) { + expect(value.extractMap('a'), isA()); + }); }); test('one level', () { - final map = { + testImmutableMap({ 'a': {'b': 2} - }; - final ap = map.extractMap('a'); - expect(ap.toNullable(), equals({'b': 2})); + }, (value) { + expect(value.extractMap('a').toNullable(), equals({'b': 2})); + }); }); test('two levels', () { - final map = { + testImmutableMap({ 'a': { 'b': {'c': 3} } - }; - final ap = map.extractMap('a').extractMap('b'); - expect(ap.toNullable(), equals({'c': 3})); + }, (value) { + expect(value.extractMap('a').extractMap('b').toNullable(), + equals({'c': 3})); + }); }); test('two levels with extract', () { - final map = { + testImmutableMap({ 'a': { 'b': {'c': 3} } - }; - final ap = map.extractMap('a').extractMap('b').extract('c'); - expect(ap, Option.of(3)); + }, (value) { + value + .extractMap('a') + .extractMap('b') + .extract('c') + .matchTestSome((t) { + expect(t, 3); + }); + }); }); }); - test('member', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - expect(map.containsKey('b'), true); - expect(map.containsKey('e'), false); - }); - - test('elem', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - expect(map.containsValue(1), true); - expect(map.containsValue(0), false); - }); - group('modifyAt', () { test('Some', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.modifyAt( - Eq.instance((a1, a2) => a1 == a2), - (v) => v + 2, - 'b', - ); - ap.matchTestSome((t) => t.lookup('b').matchTestSome((t) { - expect(t, 4); - })); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value + .modifyAt( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'b', + ) + .matchTestSome( + (t) => t.lookup('b').matchTestSome((t) { + expect(t, 4); + }), + ); + }); }); test('None', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.modifyAt( - Eq.instance((a1, a2) => a1 == a2), - (v) => v + 2, - 'e', - ); - expect(ap, isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.modifyAt( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'e', + ), + isA(), + ); + }); }); }); group('modifyAtIfPresent', () { test('found', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.modifyAtIfPresent( - Eq.instance((a1, a2) => a1 == a2), - (v) => v + 2, - 'b', - ); - ap.lookup('b').matchTestSome((t) { - expect(t, 4); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value + .modifyAtIfPresent( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'b', + ) + .lookup('b') + .matchTestSome((t) { + expect(t, 4); + }); }); }); test('not found', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.modifyAtIfPresent( - Eq.instance((a1, a2) => a1 == a2), - (v) => v + 2, - 'e', - ); - ap.lookup('b').matchTestSome((t) { - expect(t, 2); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value + .modifyAtIfPresent( + Eq.instance((a1, a2) => a1 == a2), + (v) => v + 2, + 'e', + ) + .lookup('b') + .matchTestSome((t) { + expect(t, 2); + }); }); }); }); group('updateAt', () { test('Some', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.updateAt(Eq.eqString(), 'b', 10); - ap.matchTestSome((t) => t.lookup('b').matchTestSome((t) { - expect(t, 10); - })); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value.updateAt(Eq.eqString(), 'b', 10).matchTestSome( + (t) => t.lookup('b').matchTestSome((t) { + expect(t, 10); + }), + ); + }); }); test('None', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.updateAt(Eq.eqString(), 'e', 10); - expect(ap, isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect(value.updateAt(Eq.eqString(), 'e', 10), isA()); + }); }); }); group('updateAtIfPresent', () { test('found', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = - map.updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2), 'b', 10); - ap.lookup('b').matchTestSome((t) { - expect(t, 10); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value + .updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2), 'b', 10) + .lookup('b') + .matchTestSome((t) { + expect(t, 10); + }); }); }); test('not found', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = - map.updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2), 'e', 10); - ap.lookup('b').matchTestSome((t) { - expect(t, 2); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value + .updateAtIfPresent(Eq.instance((a1, a2) => a1 == a2), 'e', 10) + .lookup('b') + .matchTestSome((t) { + expect(t, 2); + }); }); }); }); test('deleteAt', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - expect(map.lookup('b'), isA()); - final ap = map.deleteAt(Eq.instance((a1, a2) => a1 == a2), 'b'); - expect(map.lookup('b'), isA()); - expect(ap.lookup('b'), isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect(value.lookup('b'), isA()); + + final result = value.deleteAt(Eq.instance((a1, a2) => a1 == a2), 'b'); + expect(value.lookup('b'), isA()); + expect(result.lookup('b'), isA()); + }); }); group('upsertAt', () { test('insert', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - expect(map.lookup('e'), isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect(value.lookup('e'), isA()); - final ap = map.upsertAt(Eq.instance((a1, a2) => a1 == a2), 'e', 10); - expect(map.lookup('e'), isA()); + final result = + value.upsertAt(Eq.instance((a1, a2) => a1 == a2), 'e', 10); + expect(value.lookup('e'), isA()); - ap.lookup('e').matchTestSome((t) { - expect(t, 10); + result.lookup('e').matchTestSome((t) { + expect(t, 10); + }); }); }); test('update', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - map.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { - expect(t, 2); - }); - final ap = map.upsertAt(Eq.eqString(), 'b', 10); - map.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { - expect(t, 2); - }); - ap.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { - expect(t, 10); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + value.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { + expect(t, 2); + }); + + final result = value.upsertAt(Eq.eqString(), 'b', 10); + value.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { + expect(t, 2); + }); + result.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { + expect(t, 10); + }); }); }); test('modify by eq date year', () { - final d1 = DateTime(2001, 1, 1); - final d2 = DateTime(2001, 1, 2); - final map = {} - .upsertAt(dateEqYear, d1, 1) - .upsertAt(dateEqYear, d2, 2); - - map.lookupEq(dateEqYear, d1).matchTestSome((t) { - expect(t, 2); - }); - map.lookupEq(dateEqYear, d2).matchTestSome((t) { - expect(t, 2); + testImmutableMap({}, (value) { + final d1 = DateTime(2001, 1, 1); + final d2 = DateTime(2001, 1, 2); + + final result = value + .upsertAt( + dateEqYear, + d1, + 1, + ) + .upsertAt( + dateEqYear, + d2, + 2, + ); + + result.lookupEq(dateEqYear, d1).matchTestSome((t) { + expect(t, 2); + }); + result.lookupEq(dateEqYear, d2).matchTestSome((t) { + expect(t, 2); + }); }); }); }); group('pop', () { test('Some', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.pop(Eq.instance((a1, a2) => a1 == a2), 'b'); - expect(map.lookup('b'), isA()); - ap.matchTestSome((t) { - expect(t.$1, 2); - expect(t.$2.lookup('b'), isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + final result = value.pop(Eq.instance((a1, a2) => a1 == a2), 'b'); + expect(value.lookup('b'), isA()); + + result.matchTestSome((t) { + expect(t.$1, 2); + expect(t.$2.lookup('b'), isA()); + }); }); }); test('None', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.pop(Eq.instance((a1, a2) => a1 == a2), 'e'); - expect(ap, isA()); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.pop(Eq.instance((a1, a2) => a1 == a2), 'e'), + isA(), + ); + }); }); }); test('foldLeft', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = - map.foldLeft(Order.allEqual(), '', (acc, a) => '$acc$a'); - expect(ap, '1234'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldLeft(Order.allEqual(), '', (acc, a) => '$acc$a'), + '1234', + ); + }); }); test('foldLeftWithIndex', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldLeftWithIndex( - Order.allEqual(), '', (acc, a, i) => '$acc$a$i'); - expect(ap, '10213243'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldLeftWithIndex( + Order.allEqual(), '', (acc, a, i) => '$acc$a$i'), + '10213243', + ); + }); }); test('foldLeftWithKey', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldLeftWithKey( - Order.allEqual(), '', (acc, k, v) => '$acc$k$v'); - expect(ap, 'a1b2c3d4'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldLeftWithKey( + Order.allEqual(), '', (acc, k, v) => '$acc$k$v'), + 'a1b2c3d4', + ); + }); }); test('foldLeftWithKeyAndIndex', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldLeftWithKeyAndIndex( - Order.allEqual(), '', (acc, k, v, i) => '$acc$k$v$i'); - expect(ap, 'a10b21c32d43'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldLeftWithKeyAndIndex( + Order.allEqual(), '', (acc, k, v, i) => '$acc$k$v$i'), + 'a10b21c32d43', + ); + }); }); test('foldRight', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = - map.foldRight(Order.allEqual(), '', (a, acc) => '$acc$a'); - expect(ap, '4321'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldRight(Order.allEqual(), '', (a, acc) => '$acc$a'), + '4321', + ); + }); }); test('foldRightWithIndex', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldRightWithIndex( - Order.allEqual(), '', (a, acc, i) => '$acc$a$i'); - expect(ap, '40312213'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldRightWithIndex( + Order.allEqual(), '', (a, acc, i) => '$acc$a$i'), + '40312213', + ); + }); }); test('foldRightWithKey', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldRightWithKey( - Order.allEqual(), '', (k, v, acc) => '$acc$k$v'); - expect(ap, 'd4c3b2a1'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldRightWithKey( + Order.allEqual(), '', (k, v, acc) => '$acc$k$v'), + 'd4c3b2a1', + ); + }); }); test('foldRightWithKeyAndIndex', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.foldRightWithKeyAndIndex( - Order.allEqual(), '', (k, v, acc, i) => '$acc$k$v$i'); - expect(ap, 'd40c31b22a13'); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect( + value.foldRightWithKeyAndIndex( + Order.allEqual(), '', (k, v, acc, i) => '$acc$k$v$i'), + 'd40c31b22a13', + ); + }); }); test('size', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.size; - expect(ap, 4); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + expect(value.size, 4); + }); }); test('toSortedList', () { - final map = {'c': 3, 'd': 4, 'a': 1, 'b': 2}; - final ap = map.toSortedList(Order.from((a1, a2) => a1.compareTo(a2))); - expect(ap.elementAt(0).value, 1); - expect(ap.elementAt(1).value, 2); - expect(ap.elementAt(2).value, 3); - expect(ap.elementAt(3).value, 4); - expect(ap.elementAt(0).key, 'a'); - expect(ap.elementAt(1).key, 'b'); - expect(ap.elementAt(2).key, 'c'); - expect(ap.elementAt(3).key, 'd'); + testImmutableMap({'c': 3, 'd': 4, 'a': 1, 'b': 2}, (value) { + final result = + value.toSortedList(Order.from((a1, a2) => a1.compareTo(a2))); + expect(result.elementAt(0).value, 1); + expect(result.elementAt(1).value, 2); + expect(result.elementAt(2).value, 3); + expect(result.elementAt(3).value, 4); + expect(result.elementAt(0).key, 'a'); + expect(result.elementAt(1).key, 'b'); + expect(result.elementAt(2).key, 'c'); + expect(result.elementAt(3).key, 'd'); + }); }); test('union', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final map1 = {'c': 20, 'e': 10}; - final ap = - map.union(Eq.instance((a1, a2) => a1 == a2), (x, y) => x + y, map1); - expect(ap['a'], 1); - expect(ap['b'], 2); - expect(ap['c'], 23); - expect(ap['d'], 4); - expect(ap['e'], 10); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { + testImmutableMap({'c': 20, 'e': 10}, (value2) { + final ap = value1.union( + Eq.instance((a1, a2) => a1 == a2), + (x, y) => x + y, + value2, + ); + + expect(ap['a'], 1); + expect(ap['b'], 2); + expect(ap['c'], 23); + expect(ap['d'], 4); + expect(ap['e'], 10); + }); + }); }); test('intersection', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final map1 = {'c': 20, 'e': 10}; - final ap = map.intersection( - Eq.instance((a1, a2) => a1 == a2), (x, y) => x + y, map1); - expect(ap['a'], null); - expect(ap['b'], null); - expect(ap['c'], 23); - expect(ap['d'], null); - expect(ap['e'], null); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { + testImmutableMap({'c': 20, 'e': 10}, (value2) { + final ap = value1.intersection( + Eq.instance((a1, a2) => a1 == a2), + (x, y) => x + y, + value2, + ); + + expect(ap['a'], null); + expect(ap['b'], null); + expect(ap['c'], 23); + expect(ap['d'], null); + expect(ap['e'], null); + }); + }); }); group('isSubmap', () { test('true', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final map1 = {'a': 1, 'c': 3}; - final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2), - Eq.instance((a1, a2) => a1 == a2), map); - expect(ap, true); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { + testImmutableMap({'a': 1, 'c': 3}, (value2) { + final result = value2.isSubmap( + Eq.eqString(), + Eq.eqInt(), + value1, + ); + + expect(result, true); + }); + }); }); test('false (value)', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final map1 = {'a': 1, 'c': 2}; - final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2), - Eq.instance((a1, a2) => a1 == a2), map); - expect(ap, false); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { + testImmutableMap({'a': 1, 'c': 2}, (value2) { + final result = value2.isSubmap( + Eq.eqString(), + Eq.eqInt(), + value1, + ); + + expect(result, false); + }); + }); }); test('false (key)', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final map1 = {'a': 1, 'd': 3}; - final ap = map1.isSubmap(Eq.instance((a1, a2) => a1 == a2), - Eq.instance((a1, a2) => a1 == a2), map); - expect(ap, false); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { + testImmutableMap({'a': 1, 'd': 3}, (value2) { + final result = value2.isSubmap( + Eq.eqString(), + Eq.eqInt(), + value1, + ); + + expect(result, false); + }); + }); }); }); test('collect', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final ap = map.collect( + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { + final result = value.collect( Order.from( (a1, a2) => a1.compareTo(a2), ), - (k, v) => '$k$v'); - expect(ap.elementAt(0), 'a1'); - expect(ap.elementAt(1), 'b2'); - expect(ap.elementAt(2), 'c3'); - expect(ap.elementAt(3), 'd4'); + (k, v) => '$k$v', + ); + + expect(result.elementAt(0), 'a1'); + expect(result.elementAt(1), 'b2'); + expect(result.elementAt(2), 'c3'); + expect(result.elementAt(3), 'd4'); + }); }); test('difference', () { - final map = {'a': 1, 'b': 2, 'c': 3, 'd': 4}; - final map1 = {'a': 1, 'c': 3}; - final ap = map.difference(Eq.instance((a1, a2) => a1 == a2), map1); - expect(ap['a'], null); - expect(ap['b'], 2); - expect(ap['c'], null); - expect(ap['d'], 4); + testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { + testImmutableMap({'a': 1, 'c': 3}, (value2) { + final result = value1.difference(Eq.eqString(), value2); + expect(result['a'], null); + expect(result['b'], 2); + expect(result['c'], null); + expect(result['d'], 4); + }); + }); }); }); } diff --git a/packages/fpdart/test/src/utils/collection_utils.dart b/packages/fpdart/test/src/utils/collection_utils.dart new file mode 100644 index 00000000..fa90b144 --- /dev/null +++ b/packages/fpdart/test/src/utils/collection_utils.dart @@ -0,0 +1,18 @@ +import 'package:collection/collection.dart'; +import 'package:test/test.dart'; + +final objectDeepEquality = const DeepCollectionEquality().equals; + +void testImmutableMap( + Map source, + void Function(Map value) test, +) { + final originalSource = {...source}; + test(source); + expect( + objectDeepEquality(originalSource, source), + true, + reason: + "The provided element is not immutable: ${source} should be ${originalSource}", + ); +} diff --git a/packages/fpdart/test/src/utils/utils.dart b/packages/fpdart/test/src/utils/utils.dart index 92ee4610..f05a0172 100644 --- a/packages/fpdart/test/src/utils/utils.dart +++ b/packages/fpdart/test/src/utils/utils.dart @@ -1,5 +1,6 @@ export 'package:glados/glados.dart'; export './async_utils.dart'; +export './collection_utils.dart'; export './glados_utils.dart'; export './match_utils.dart'; From a110e71464fd85707546e6c880b284e1a14c5255 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 24 May 2023 18:40:05 +0200 Subject: [PATCH 47/71] introduce ReaderTaskEither --- .../src/reader_task_either/overview.dart | 21 ++ packages/fpdart/lib/fpdart.dart | 1 + .../fpdart/lib/src/reader_task_either.dart | 260 ++++++++++++++++++ packages/fpdart/lib/src/typeclass/hkt.dart | 4 + 4 files changed, 286 insertions(+) create mode 100644 packages/fpdart/example/src/reader_task_either/overview.dart create mode 100644 packages/fpdart/lib/src/reader_task_either.dart diff --git a/packages/fpdart/example/src/reader_task_either/overview.dart b/packages/fpdart/example/src/reader_task_either/overview.dart new file mode 100644 index 00000000..528af591 --- /dev/null +++ b/packages/fpdart/example/src/reader_task_either/overview.dart @@ -0,0 +1,21 @@ +import 'package:fpdart/fpdart.dart'; + +typedef Env = (int, String); +typedef Error = String; +typedef Success = int; + +void main(List args) async { + final rte = ReaderTaskEither.Do(($) async { + final a = 10; + final val = await $(ReaderTaskEither.rightReader( + Reader( + (env) => env.$1 + env.$2.length, + ), + )); + + return a + val; + }); + + final result = await rte.run((30, "abc")); + print(result); +} diff --git a/packages/fpdart/lib/fpdart.dart b/packages/fpdart/lib/fpdart.dart index 9770d9fd..b3a6d154 100644 --- a/packages/fpdart/lib/fpdart.dart +++ b/packages/fpdart/lib/fpdart.dart @@ -9,6 +9,7 @@ export 'src/io_ref.dart'; export 'src/option.dart'; export 'src/random.dart'; export 'src/reader.dart'; +export 'src/reader_task_either.dart'; export 'src/state.dart'; export 'src/state_async.dart'; export 'src/task.dart'; diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart new file mode 100644 index 00000000..9947d93d --- /dev/null +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -0,0 +1,260 @@ +import 'either.dart'; +import 'function.dart'; +import 'option.dart'; +import 'reader.dart'; +import 'task.dart'; +import 'task_either.dart'; +import 'typeclass/hkt.dart'; + +final class _ReaderTaskEitherThrow { + final L value; + const _ReaderTaskEitherThrow(this.value); +} + +typedef DoAdapterReaderTaskEither = Future Function( + ReaderTaskEither); +DoAdapterReaderTaskEither _doAdapter(E env) => + (readerTaskEither) => readerTaskEither.run(env).then( + (either) => either.getOrElse((l) => throw _ReaderTaskEitherThrow(l)), + ); + +typedef DoFunctionReaderTaskEither = Future Function( + DoAdapterReaderTaskEither $); + +/// Tag the [HKT3] interface for the actual [ReaderTaskEither]. +abstract final class _ReaderTaskEitherHKT {} + +/// `ReaderTaskEither` represents an asynchronous computation that +/// either yields a value of type `R` or fails yielding an error of type `L`. +/// +/// If you want to represent an asynchronous computation that never fails, see [Task]. +final class ReaderTaskEither + extends HKT3<_ReaderTaskEitherHKT, E, L, R> +// with +// Functor2<_ReaderTaskEitherHKT, L, R>, +// Applicative2<_ReaderTaskEitherHKT, L, R>, +// Monad2<_ReaderTaskEitherHKT, L, R>, +// Alt2<_ReaderTaskEitherHKT, L, R> +{ + final Future> Function(E env) _run; + + /// Build a [ReaderTaskEither] from a function returning a `Future>`. + const ReaderTaskEither(this._run); + + /// Initialize a **Do Notation** chain. + // ignore: non_constant_identifier_names + factory ReaderTaskEither.Do(DoFunctionReaderTaskEither f) => + ReaderTaskEither((env) async { + try { + return Either.of(await f(_doAdapter(env))); + } on _ReaderTaskEitherThrow catch (e) { + return Either.left(e.value); + } + }); + + /// Run the task given `E` and return a `Future>`. + Future> run(E env) => _run(env); + + /// Returns a [ReaderTaskEither] that returns a `Right(a)`. + ReaderTaskEither pure(C a) => ReaderTaskEither( + (_) async => Right(a), + ); + + /// Used to chain multiple functions that return a [ReaderTaskEither]. + /// + /// You can extract the value of every [Right] in the chain without + /// handling all possible missing cases. + /// + /// If running any of the tasks in the chain returns [Left], the result is [Left]. + ReaderTaskEither flatMap( + covariant ReaderTaskEither Function(R r) f, + ) => + ReaderTaskEither((env) => run(env).then( + (either) async => either.match( + left, + (r) => f(r).run(env), + ), + )); + + /// Chain a function that takes the current value `R` inside this [TaskEither] + /// and returns [Either]. + /// + /// Similar to `flatMap`, but `f` returns [Either] instead of [TaskEither]. + ReaderTaskEither flatMapTaskEither( + TaskEither Function(R r) f, + ) => + ReaderTaskEither((env) => run(env).then( + (either) async => either.match( + left, + (r) => f(r).run(), + ), + )); + + /// If running this [ReaderTaskEither] returns [Right], then return the result of calling `then`. + /// Otherwise return [Left]. + ReaderTaskEither andThen( + covariant ReaderTaskEither Function() then, + ) => + flatMap((_) => then()); + + /// If running this [ReaderTaskEither] returns [Right], then change its value from type `R` to + /// type `C` using function `f`. + ReaderTaskEither map(C Function(R r) f) => ap(pure(f)); + + /// Change the value in the [Left] of [ReaderTaskEither]. + ReaderTaskEither mapLeft(C Function(L l) f) => ReaderTaskEither( + (env) async => (await run(env)).match( + (l) => Either.left(f(l)), + Either.of, + ), + ); + + /// Define two functions to change both the [Left] and [Right] value of the + /// [ReaderTaskEither]. + ReaderTaskEither bimap( + 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`. + ReaderTaskEither ap( + covariant ReaderTaskEither a, + ) => + a.flatMap( + (f) => flatMap( + (v) => pure(f(v)), + ), + ); + + /// Chain multiple functions having the same left type `L`. + ReaderTaskEither call( + covariant ReaderTaskEither chain, + ) => + flatMap((_) => chain); + + /// Change this [ReaderTaskEither] from `ReaderTaskEither` to `ReaderTaskEither`. + ReaderTaskEither swap() => ReaderTaskEither( + (env) async => (await run(env)).match(right, left), + ); + + /// When this [ReaderTaskEither] returns [Right], then return the current [ReaderTaskEither]. + /// Otherwise return the result of `orElse`. + /// + /// Used to provide an **alt**ernative [ReaderTaskEither] in case the current one returns [Left]. + ReaderTaskEither alt( + covariant ReaderTaskEither Function() orElse, + ) => + ReaderTaskEither( + (env) async => (await run(env)).match( + (_) => orElse().run(env), + right, + ), + ); + + /// If `f` applied on this [ReaderTaskEither] as [Right] returns `true`, then return this [ReaderTaskEither]. + /// If it returns `false`, return the result of `onFalse` in a [Left]. + ReaderTaskEither filterOrElse( + bool Function(R r) f, + L Function(R r) onFalse, + ) => + flatMap( + (r) => + f(r) ? ReaderTaskEither.of(r) : ReaderTaskEither.left(onFalse(r)), + ); + + /// When this [ReaderTaskEither] returns a [Left] then return the result of `orElse`. + /// Otherwise return this [ReaderTaskEither]. + ReaderTaskEither orElse( + ReaderTaskEither Function(L l) orElse, + ) => + ReaderTaskEither((env) async => (await run(env)).match( + (l) => orElse(l).run(env), + (r) => ReaderTaskEither.of(r).run(env), + )); + + /// Flat a [ReaderTaskEither] contained inside another [ReaderTaskEither] to be a single [ReaderTaskEither]. + factory ReaderTaskEither.flatten( + ReaderTaskEither> readerTaskEither, + ) => + readerTaskEither.flatMap(identity); + + /// Build a [ReaderTaskEither] from a `Reader`. + factory ReaderTaskEither.rightReader(Reader reader) => ReaderTaskEither( + (env) async => Right(reader.run(env)), + ); + + /// Build a [ReaderTaskEither] from a `Reader`. + factory ReaderTaskEither.leftReader(Reader reader) => ReaderTaskEither( + (env) async => Left(reader.run(env)), + ); + + /// Build a [ReaderTaskEither] that returns a `Left(left)`. + factory ReaderTaskEither.left(L left) => ReaderTaskEither( + (_) async => Left(left), + ); + + /// Build a [ReaderTaskEither] that returns a [Left] containing the result of running `task`. + factory ReaderTaskEither.leftTask(Task task) => ReaderTaskEither( + (_) => task.run().then(left), + ); + + /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `task`. + /// + /// Same of `ReaderTaskEither.fromTask` + factory ReaderTaskEither.fromTask(Task task) => ReaderTaskEither( + (_) async => Right(await task.run()), + ); + + /// {@template fpdart_from_nullable_reader_task_either} + /// If `r` is `null`, then return the result of `onNull` in [Left]. + /// Otherwise return `Right(r)`. + /// {@endtemplate} + factory ReaderTaskEither.fromNullable(R? r, L Function() onNull) => + ReaderTaskEither( + (_) async => Either.fromNullable(r, onNull), + ); + + /// {@macro fpdart_from_nullable_reader_task_either} + factory ReaderTaskEither.fromNullableAsync(R? r, Task onNull) => + ReaderTaskEither( + (_) async => r != null ? Either.of(r) : Either.left(await onNull.run()), + ); + + /// Build a [ReaderTaskEither] from `option`. + /// + /// When `option` is [Some], then return [Right] when + /// running [ReaderTaskEither]. Otherwise return `onNone`. + factory ReaderTaskEither.fromOption(Option option, L Function() onNone) => + ReaderTaskEither((_) async => option.match( + () => Left(onNone()), + Right.new, + )); + + /// Build a [ReaderTaskEither] that returns `either`. + factory ReaderTaskEither.fromEither(Either either) => + ReaderTaskEither((_) async => either); + + /// Build a [ReaderTaskEither] that returns a `Right(r)`. + factory ReaderTaskEither.of(R r) => ReaderTaskEither( + (_) async => Either.of(r), + ); + + /// 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`. + factory ReaderTaskEither.tryCatch( + Future Function() run, + L Function(Object error, StackTrace stackTrace) onError, + ) => + ReaderTaskEither((_) async { + try { + return Right(await run()); + } catch (error, stack) { + return Left(onError(error, stack)); + } + }); +} diff --git a/packages/fpdart/lib/src/typeclass/hkt.dart b/packages/fpdart/lib/src/typeclass/hkt.dart index b6117255..7f70d3bc 100644 --- a/packages/fpdart/lib/src/typeclass/hkt.dart +++ b/packages/fpdart/lib/src/typeclass/hkt.dart @@ -14,3 +14,7 @@ abstract class HKT { abstract class HKT2 { const HKT2(); } + +abstract class HKT3 { + const HKT3(); +} From 81414577f62be9582b588cb1a5e9bc08b0affbd0 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 25 May 2023 12:54:54 +0200 Subject: [PATCH 48/71] ask and asks --- .../example/src/reader_task_either/overview.dart | 6 ++++-- packages/fpdart/lib/src/reader.dart | 4 ++-- packages/fpdart/lib/src/reader_task_either.dart | 10 ++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/fpdart/example/src/reader_task_either/overview.dart b/packages/fpdart/example/src/reader_task_either/overview.dart index 528af591..b95845fd 100644 --- a/packages/fpdart/example/src/reader_task_either/overview.dart +++ b/packages/fpdart/example/src/reader_task_either/overview.dart @@ -2,7 +2,7 @@ import 'package:fpdart/fpdart.dart'; typedef Env = (int, String); typedef Error = String; -typedef Success = int; +typedef Success = String; void main(List args) async { final rte = ReaderTaskEither.Do(($) async { @@ -12,8 +12,10 @@ void main(List args) async { (env) => env.$1 + env.$2.length, ), )); + final env = await $(ReaderTaskEither.ask()); + final env2 = await $(ReaderTaskEither.asks((dep) => dep.$2)); - return a + val; + return "$a and $val and $env and $env2"; }); final result = await rte.run((30, "abc")); diff --git a/packages/fpdart/lib/src/reader.dart b/packages/fpdart/lib/src/reader.dart index 23da4db0..047a4ac7 100644 --- a/packages/fpdart/lib/src/reader.dart +++ b/packages/fpdart/lib/src/reader.dart @@ -72,11 +72,11 @@ final class Reader extends HKT2 /// Change dependency type of `Reader` from `R` to `R1` after calling `run`. Reader local(R Function(R1 context) f) => Reader((r) => run(f(r))); - /// Read the current dependecy `R`. + /// Read the current dependency `R`. Reader ask() => Reader(identity); /// Change reading function to `f` given context/dependency `R`. - Reader asks(A Function(R r) f) => Reader((r) => f(r)); + Reader asks(A Function(R r) f) => Reader(f); /// Chain a request that returns another [Reader], execute it, ignore /// the result, and return the same value as the current [Reader]. diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart index 9947d93d..6afe3ba1 100644 --- a/packages/fpdart/lib/src/reader_task_either.dart +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -257,4 +257,14 @@ final class ReaderTaskEither return Left(onError(error, stack)); } }); + + /// Extract a value `A` given the current dependency `E`. + factory ReaderTaskEither.asks(R Function(E) f) => ReaderTaskEither( + (env) async => right(f(env)), + ); + + /// Read the current dependency `E`. + static ReaderTaskEither ask() => ReaderTaskEither( + (env) async => right(env), + ); } From 2e78542b168a59375690506346aae5c31398c71d Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 25 May 2023 18:44:56 +0200 Subject: [PATCH 49/71] test and typeclass ReaderTaskEither --- .../fpdart/lib/src/reader_task_either.dart | 51 +- packages/fpdart/lib/src/task_either.dart | 1 + packages/fpdart/lib/src/typeclass/alt.dart | 4 + .../fpdart/lib/src/typeclass/applicative.dart | 12 +- .../fpdart/lib/src/typeclass/functor.dart | 4 + packages/fpdart/lib/src/typeclass/monad.dart | 43 + .../test/src/reader_task_either_test.dart | 1003 +++++++++++++++++ 7 files changed, 1107 insertions(+), 11 deletions(-) create mode 100644 packages/fpdart/test/src/reader_task_either_test.dart diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart index 6afe3ba1..280389f2 100644 --- a/packages/fpdart/lib/src/reader_task_either.dart +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -4,7 +4,11 @@ import 'option.dart'; import 'reader.dart'; import 'task.dart'; import 'task_either.dart'; +import 'typeclass/alt.dart'; +import 'typeclass/applicative.dart'; +import 'typeclass/functor.dart'; import 'typeclass/hkt.dart'; +import 'typeclass/monad.dart'; final class _ReaderTaskEitherThrow { final L value; @@ -30,12 +34,11 @@ abstract final class _ReaderTaskEitherHKT {} /// If you want to represent an asynchronous computation that never fails, see [Task]. final class ReaderTaskEither extends HKT3<_ReaderTaskEitherHKT, E, L, R> -// with -// Functor2<_ReaderTaskEitherHKT, L, R>, -// Applicative2<_ReaderTaskEitherHKT, L, R>, -// Monad2<_ReaderTaskEitherHKT, L, R>, -// Alt2<_ReaderTaskEitherHKT, L, R> -{ + with + Functor3<_ReaderTaskEitherHKT, E, L, R>, + Applicative3<_ReaderTaskEitherHKT, E, L, R>, + Monad3<_ReaderTaskEitherHKT, E, L, R>, + Alt3<_ReaderTaskEitherHKT, E, L, R> { final Future> Function(E env) _run; /// Build a [ReaderTaskEither] from a function returning a `Future>`. @@ -56,6 +59,7 @@ final class ReaderTaskEither Future> run(E env) => _run(env); /// Returns a [ReaderTaskEither] that returns a `Right(a)`. + @override ReaderTaskEither pure(C a) => ReaderTaskEither( (_) async => Right(a), ); @@ -66,6 +70,7 @@ final class ReaderTaskEither /// handling all possible missing cases. /// /// If running any of the tasks in the chain returns [Left], the result is [Left]. + @override ReaderTaskEither flatMap( covariant ReaderTaskEither Function(R r) f, ) => @@ -92,6 +97,7 @@ final class ReaderTaskEither /// If running this [ReaderTaskEither] returns [Right], then return the result of calling `then`. /// Otherwise return [Left]. + @override ReaderTaskEither andThen( covariant ReaderTaskEither Function() then, ) => @@ -99,8 +105,26 @@ final class ReaderTaskEither /// If running this [ReaderTaskEither] returns [Right], then change its value from type `R` to /// type `C` using function `f`. + @override ReaderTaskEither map(C Function(R r) f) => ap(pure(f)); + @override + ReaderTaskEither map2( + covariant ReaderTaskEither m1, + N1 Function(R p1, N1 p2) f, + ) => + flatMap((b) => m1.map((c) => f(b, c))); + + @override + ReaderTaskEither map3( + covariant ReaderTaskEither m1, + covariant ReaderTaskEither m2, + N2 Function(R p1, N1 p2, N2 p3) f, + ) => + flatMap( + (b) => m1.flatMap((c) => m2.map((d) => f(b, c, d))), + ); + /// Change the value in the [Left] of [ReaderTaskEither]. ReaderTaskEither mapLeft(C Function(L l) f) => ReaderTaskEither( (env) async => (await run(env)).match( @@ -119,6 +143,7 @@ final class ReaderTaskEither /// Apply the function contained inside `a` to change the value on the [Right] from /// type `R` to a value of type `C`. + @override ReaderTaskEither ap( covariant ReaderTaskEither a, ) => @@ -128,7 +153,14 @@ final class ReaderTaskEither ), ); + @override + ReaderTaskEither chainFirst( + covariant ReaderTaskEither Function(R p1) chain, + ) => + flatMap((b) => chain(b).map((c) => b)); + /// Chain multiple functions having the same left type `L`. + @override ReaderTaskEither call( covariant ReaderTaskEither chain, ) => @@ -143,6 +175,7 @@ final class ReaderTaskEither /// Otherwise return the result of `orElse`. /// /// Used to provide an **alt**ernative [ReaderTaskEither] in case the current one returns [Left]. + @override ReaderTaskEither alt( covariant ReaderTaskEither Function() orElse, ) => @@ -247,12 +280,12 @@ final class ReaderTaskEither /// /// Used to work with [Future] and exceptions using [Either] instead of `try`/`catch`. factory ReaderTaskEither.tryCatch( - Future Function() run, + Future Function(E) run, L Function(Object error, StackTrace stackTrace) onError, ) => - ReaderTaskEither((_) async { + ReaderTaskEither((env) async { try { - return Right(await run()); + return Right(await run(env)); } catch (error, stack) { return Left(onError(error, stack)); } diff --git a/packages/fpdart/lib/src/task_either.dart b/packages/fpdart/lib/src/task_either.dart index 5e4e2433..99793cf5 100644 --- a/packages/fpdart/lib/src/task_either.dart +++ b/packages/fpdart/lib/src/task_either.dart @@ -218,6 +218,7 @@ final class TaskEither extends HKT2<_TaskEitherHKT, L, R> /// {@template fpdart_from_nullable_task_either} /// If `r` is `null`, then return the result of `onNull` in [Left]. /// Otherwise return `Right(r)`. + /// {@endtemplate} factory TaskEither.fromNullable(R? r, L Function() onNull) => Either.fromNullable(r, onNull).toTaskEither(); diff --git a/packages/fpdart/lib/src/typeclass/alt.dart b/packages/fpdart/lib/src/typeclass/alt.dart index 3c5c6c56..6227f16b 100644 --- a/packages/fpdart/lib/src/typeclass/alt.dart +++ b/packages/fpdart/lib/src/typeclass/alt.dart @@ -12,3 +12,7 @@ mixin Alt on HKT, Functor { mixin Alt2 on HKT2, Functor2 { HKT2 alt(HKT2 Function() orElse); } + +mixin Alt3 on HKT3, Functor3 { + HKT3 alt(HKT3 Function() orElse); +} diff --git a/packages/fpdart/lib/src/typeclass/applicative.dart b/packages/fpdart/lib/src/typeclass/applicative.dart index 0b6d0103..9856a49c 100644 --- a/packages/fpdart/lib/src/typeclass/applicative.dart +++ b/packages/fpdart/lib/src/typeclass/applicative.dart @@ -2,7 +2,7 @@ import 'functor.dart'; import 'hkt.dart'; mixin Applicative on HKT, Functor { - HKT pure(B a); + HKT pure(B b); HKT ap(HKT a); @override @@ -10,9 +10,17 @@ mixin Applicative on HKT, Functor { } mixin Applicative2 on HKT2, Functor2 { - HKT2 pure(C a); + HKT2 pure(C c); HKT2 ap(HKT2 a); @override HKT2 map(C Function(B a) f) => ap(pure(f)); } + +mixin Applicative3 on HKT3, Functor3 { + HKT3 pure(C a); + HKT3 ap(HKT3 a); + + @override + HKT3 map(D Function(C c) f) => ap(pure(f)); +} diff --git a/packages/fpdart/lib/src/typeclass/functor.dart b/packages/fpdart/lib/src/typeclass/functor.dart index 20413e35..648b5561 100644 --- a/packages/fpdart/lib/src/typeclass/functor.dart +++ b/packages/fpdart/lib/src/typeclass/functor.dart @@ -11,3 +11,7 @@ mixin Functor on HKT { mixin Functor2 on HKT2 { HKT2 map(C Function(B b) f); } + +mixin Functor3 on HKT3 { + HKT3 map(D Function(C c) f); +} diff --git a/packages/fpdart/lib/src/typeclass/monad.dart b/packages/fpdart/lib/src/typeclass/monad.dart index c013d0ba..474a7548 100644 --- a/packages/fpdart/lib/src/typeclass/monad.dart +++ b/packages/fpdart/lib/src/typeclass/monad.dart @@ -51,3 +51,46 @@ mixin Monad2 on HKT2, Applicative2 { HKT2 call(HKT2 chain) => flatMap((_) => chain); } + +mixin Monad3 + on HKT3, Applicative3 { + HKT3 flatMap(HKT3 Function(P3) f); + + /// Derive `ap` from `flatMap`. + /// + /// Use `flatMap` to extract the value from `a` and from the current [Monad]. + /// If both these values are present, apply the function from `a` to the value + /// of the current [Monad], using `pure` to return the correct type. + @override + HKT3 ap( + covariant Monad3 a) => + a.flatMap((f) => flatMap((v) => pure(f(v)))); + + HKT3 map2( + Monad3 m1, + N1 Function(P3, N1) f, + ) => + flatMap((b) => m1.map((c) => f(b, c))); + + HKT3 map3( + Monad3 m1, + Monad3 m2, + N2 Function(P3, N1, N2) f, + ) => + flatMap( + (b) => m1.flatMap((c) => m2.map((d) => f(b, c, d))), + ); + + HKT3 andThen( + HKT3 Function() then, + ) => + flatMap((_) => then()); + + HKT3 chainFirst( + covariant Monad3 Function(P3) chain, + ) => + flatMap((b) => chain(b).map((c) => b)); + + HKT3 call(HKT3 chain) => + flatMap((_) => chain); +} diff --git a/packages/fpdart/test/src/reader_task_either_test.dart b/packages/fpdart/test/src/reader_task_either_test.dart new file mode 100644 index 00000000..f35b95d3 --- /dev/null +++ b/packages/fpdart/test/src/reader_task_either_test.dart @@ -0,0 +1,1003 @@ +import 'package:fpdart/fpdart.dart'; + +import './utils/utils.dart'; + +void main() { + group('ReaderTaskEither', () { + group('tryCatch', () { + test('Success', () async { + final apply = ReaderTaskEither.tryCatch( + (env) => Future.value(env.toInt()), + (_, __) => 'error', + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); + }); + }); + + test('Failure', () async { + final apply = ReaderTaskEither.tryCatch( + (env) => Future.error(env.toInt()), + (_, __) => 'error', + ); + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "error"); + }); + }); + + test('throws Exception', () async { + final apply = ReaderTaskEither.tryCatch((_) { + throw UnimplementedError(); + }, (error, _) { + expect(error, isA()); + return 'error'; + }); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "error"); + }); + }); + }); + + group('flatMap', () { + test('Right', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).flatMap( + (r) => ReaderTaskEither( + (env) async => Either.of(r + env.toInt()), + ), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 24); + }); + }); + + test('Left', () async { + final apply = ReaderTaskEither( + (env) async => Either.left("$env"), + ).flatMap( + (r) => ReaderTaskEither( + (env) async => Either.of(r + env.toInt()), + ), + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); + }); + }); + }); + + group('ap', () { + test('Right', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).ap( + ReaderTaskEither( + (env) async => Either.of((c) => c / 2), + ), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 6); + }); + }); + + test('Left', () async { + final apply = ReaderTaskEither( + (env) async => Either.left("$env"), + ).ap( + ReaderTaskEither( + (env) async => Either.of((c) => c / 2), + ), + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); + }); + }); + }); + + group('map', () { + test('Right', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).map((r) => r / 2); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 6); + }); + }); + + test('Left', () async { + final apply = ReaderTaskEither( + (env) async => Either.left('$env'), + ).map((r) => r / 2); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); + }); + }); + }); + + group('mapLeft', () { + test('Right', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).mapLeft( + (l) => '$l and more', + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); + }); + }); + + test('Left', () async { + final apply = ReaderTaskEither( + (env) async => Either.left("$env"), + ).mapLeft( + (l) => '$l and more', + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2 and more"); + }); + }); + }); + + group('bimap', () { + test('Right', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).bimap( + (l) => '$l and more', + (a) => a * 2, + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 24); + }); + }); + + test('Left', () async { + final apply = ReaderTaskEither( + (env) async => Either.left('$env'), + ).bimap( + (l) => '$l and more', + (a) => a * 2, + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2 and more"); + }); + }); + }); + + group('map2', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ap = task.map2( + TaskEither(() async => Either.of(2)), (b, c) => b / c); + final r = await ap.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 5.0)); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('abc')); + final ap = task.map2( + TaskEither(() async => Either.of(2)), (b, c) => b / c); + final r = await ap.run(); + r.match((l) => expect(l, 'abc'), (_) { + fail('should be left'); + }); + }); + }); + + group('map3', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ap = task.map3( + TaskEither(() async => Either.of(2)), + TaskEither(() async => Either.of(5)), + (b, c, d) => b * c / d); + final r = await ap.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 4.0)); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('abc')); + final ap = task.map3( + TaskEither(() async => Either.of(2)), + TaskEither(() async => Either.of(5)), + (b, c, d) => b * c / d); + final r = await ap.run(); + r.match((l) => expect(l, 'abc'), (_) { + fail('should be left'); + }); + }); + }); + + group('andThen', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ap = task.andThen( + () => TaskEither(() async => Either.of(12.5))); + final r = await ap.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 12.5)); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('abc')); + final ap = task.andThen( + () => TaskEither(() async => Either.of(12.5))); + final r = await ap.run(); + r.match((l) => expect(l, 'abc'), (_) { + fail('should be left'); + }); + }); + }); + + group('call', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ap = + task(TaskEither(() async => Either.of(12.5))); + final r = await ap.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 12.5)); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('abc')); + final ap = + task(TaskEither(() async => Either.of(12.5))); + final r = await ap.run(); + r.match((r) { + expect(r, 'abc'); + }, (_) { + fail('should be left'); + }); + }); + }); + + group('filterOrElse', () { + test('Right (true)', () async { + final task = TaskEither(() async => Either.of(10)); + final ap = task.filterOrElse((r) => r > 5, (r) => 'abc'); + final r = await ap.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('Right (false)', () async { + final task = TaskEither(() async => Either.of(10)); + final ap = task.filterOrElse((r) => r < 5, (r) => 'none'); + final r = await ap.run(); + r.match((l) => expect(l, 'none'), (_) { + fail('should be left'); + }); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('abc')); + final ap = task.filterOrElse((r) => r > 5, (r) => 'none'); + final r = await ap.run(); + r.match((l) => expect(l, 'abc'), (_) { + fail('should be left'); + }); + }); + }); + + test('pure', () async { + final task = TaskEither(() async => Either.left('abc')); + final ap = task.pure('abc'); + final r = await ap.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 'abc')); + }); + + test('run', () async { + final task = TaskEither(() async => Either.of(10)); + final future = task.run(); + expect(future, isA()); + final r = await future; + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + group('fromEither', () { + test('Right', () async { + final task = TaskEither.fromEither(Either.of(10)); + final r = await task.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('Left', () async { + final task = TaskEither.fromEither(Either.left('error')); + final r = await task.run(); + r.match((l) => expect(l, 'error'), (_) { + fail('should be left'); + }); + }); + }); + + group('fromOption', () { + test('Right', () async { + final task = + TaskEither.fromOption(Option.of(10), () => 'none'); + final r = await task.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('Left', () async { + final task = + TaskEither.fromOption(Option.none(), () => 'none'); + final r = await task.run(); + r.match((l) => expect(l, 'none'), (_) { + fail('should be left'); + }); + }); + }); + + group('fromNullable', () { + test('Right', () async { + final task = TaskEither.fromNullable(10, () => "Error"); + final result = await task.run(); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('Left', () async { + final task = TaskEither.fromNullable(null, () => "Error"); + final result = await task.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + }); + }); + + group('fromNullableAsync', () { + test('Right', () async { + final task = TaskEither.fromNullableAsync( + 10, Task(() async => "Error")); + final result = await task.run(); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('Left', () async { + final task = TaskEither.fromNullableAsync( + null, Task(() async => "Error")); + final result = await task.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + }); + }); + + group('fromPredicate', () { + test('True', () async { + final task = TaskEither.fromPredicate( + 20, (n) => n > 10, (n) => '$n'); + final r = await task.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 20)); + }); + + test('False', () async { + final task = TaskEither.fromPredicate( + 10, (n) => n > 10, (n) => '$n'); + final r = await task.run(); + r.match((l) => expect(l, '10'), (_) { + fail('should be left'); + }); + }); + }); + + test('fromTask', () async { + final task = TaskEither.fromTask(Task(() async => 10)); + final r = await task.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('left', () async { + final task = TaskEither.left('none'); + final r = await task.run(); + r.match((l) => expect(l, 'none'), (_) { + fail('should be left'); + }); + }); + + test('right', () async { + final task = TaskEither.right(10); + final r = await task.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('leftTask', () async { + final task = TaskEither.leftTask(Task(() async => 'none')); + final r = await task.run(); + r.match((l) => expect(l, 'none'), (_) { + fail('should be left'); + }); + }); + + test('rightTask', () async { + final task = TaskEither.rightTask(Task.of(10)); + final r = await task.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + group('match', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ex = task.match((l) => l.length, (r) => r + 10); + final r = await ex.run(); + expect(r, 20); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('none')); + final ex = task.match((l) => l.length, (r) => r + 10); + final r = await ex.run(); + expect(r, 4); + }); + }); + + group('getOrElse', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ex = task.getOrElse((l) => l.length); + final r = await ex.run(); + expect(r, 10); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('none')); + final ex = task.getOrElse((l) => l.length); + final r = await ex.run(); + expect(r, 4); + }); + }); + + group('orElse', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ex = + task.orElse((l) => TaskEither(() async => Right(l.length))); + final r = await ex.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('none')); + final ex = + task.orElse((l) => TaskEither(() async => Right(l.length))); + final r = await ex.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 4)); + }); + }); + + group('alt', () { + test('Right', () async { + final task = TaskEither(() async => Either.of(10)); + final ex = task.alt(() => TaskEither(() async => Either.of(20))); + final r = await ex.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('Left', () async { + final task = TaskEither(() async => Either.left('none')); + final ex = task.alt(() => TaskEither(() async => Either.of(20))); + final r = await ex.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 20)); + }); + }); + + test('swap', () async { + final task = TaskEither(() async => Either.of(10)); + final ex = task.swap(); + final r = await ex.run(); + r.match((l) => expect(l, 10), (_) { + fail('should be left'); + }); + }); + + test('of', () async { + final task = TaskEither.of(10); + final r = await task.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('flatten', () async { + final task = TaskEither>.of( + TaskEither.of(10)); + final ap = TaskEither.flatten(task); + final r = await ap.run(); + r.match((_) { + fail('should be right'); + }, (r) => expect(r, 10)); + }); + + test('chainFirst', () async { + final task = TaskEither.of(10); + var sideEffect = 10; + final chain = task.chainFirst((b) { + sideEffect = 100; + return TaskEither.left("abc"); + }); + final r = await chain.run(); + r.match( + (l) => fail('should be right'), + (r) { + expect(r, 10); + expect(sideEffect, 100); + }, + ); + }); + + test('delay', () async { + final task = TaskEither(() async => Either.of(10)); + final ap = task.delay(const Duration(seconds: 2)); + final stopwatch = Stopwatch(); + stopwatch.start(); + await ap.run(); + stopwatch.stop(); + expect(stopwatch.elapsedMilliseconds >= 2000, true); + }); + + group('sequenceList', () { + test('Right', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right(1); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right(2); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right(3); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right(4); + }), + ]; + final traverse = TaskEither.sequenceList(list); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, list.length); + }); + + test('Left', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right(1); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return left("Error"); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right(3); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right(4); + }), + ]; + final traverse = TaskEither.sequenceList(list); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + group('traverseList', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = TaskEither.traverseList( + list, + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right("$a"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, list.length); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = TaskEither.traverseList( + list, + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return a % 2 == 0 + ? right("$a") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + group('traverseListWithIndex', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = TaskEither.traverseListWithIndex( + list, + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return right("$a$i"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, list.length); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = TaskEither.traverseListWithIndex( + list, + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect += 1; + return a % 2 == 0 + ? right("$a$i") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, list.length); + }); + }); + + group('sequenceListSeq', () { + test('Right', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 0; + return right(1); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 1; + return right(2); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 2; + return right(3); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 3; + return right(4); + }), + ]; + final traverse = TaskEither.sequenceListSeq(list); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, [1, 2, 3, 4]); + }); + expect(sideEffect, 3); + }); + + test('Left', () async { + var sideEffect = 0; + final list = [ + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 0; + return right(1); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 1; + return left("Error"); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 2; + return right(3); + }), + TaskEither(() async { + await AsyncUtils.waitFuture(); + sideEffect = 3; + return right(4); + }), + ]; + final traverse = TaskEither.sequenceListSeq(list); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, 3); + }); + }); + + group('traverseListSeq', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = TaskEither.traverseListSeq( + list, + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a - 1; + return right("$a"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['1', '2', '3', '4', '5', '6']); + }); + expect(sideEffect, 5); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = TaskEither.traverseListSeq( + list, + (a) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a - 1; + return a % 2 == 0 + ? right("$a") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, 5); + }); + }); + + group('traverseListWithIndexSeq', () { + test('Right', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = + TaskEither.traverseListWithIndexSeq( + list, + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a + i; + return right("$a$i"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestRight((t) { + expect(t, ['10', '21', '32', '43', '54', '65']); + }); + expect(sideEffect, 11); + }); + + test('Left', () async { + final list = [1, 2, 3, 4, 5, 6]; + var sideEffect = 0; + final traverse = + TaskEither.traverseListWithIndexSeq( + list, + (a, i) => TaskEither( + () async { + await AsyncUtils.waitFuture(); + sideEffect = a + i; + return a % 2 == 0 + ? right("$a$i") + : left("Error"); + }, + ), + ); + expect(sideEffect, 0); + final result = await traverse.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + expect(sideEffect, 11); + }); + }); + + group('Do Notation', () { + test('should return the correct value', () async { + final doTaskEither = + TaskEither.Do(($) => $(TaskEither.of(10))); + final run = await doTaskEither.run(); + run.matchTestRight((t) { + expect(t, 10); + }); + }); + + test('should extract the correct values', () async { + final doTaskEither = TaskEither.Do(($) async { + final a = await $(TaskEither.of(10)); + final b = await $(TaskEither.of(5)); + return a + b; + }); + final run = await doTaskEither.run(); + run.matchTestRight((t) { + expect(t, 15); + }); + }); + + test('should return Left if any Either is Left', () async { + final doTaskEither = TaskEither.Do(($) async { + final a = await $(TaskEither.of(10)); + final b = await $(TaskEither.of(5)); + final c = await $(TaskEither.left('Error')); + return a + b + c; + }); + final run = await doTaskEither.run(); + run.matchTestLeft((t) { + expect(t, 'Error'); + }); + }); + + test('should rethrow if throw is used inside Do', () { + final doTaskEither = TaskEither.Do(($) { + $(TaskEither.of(10)); + throw UnimplementedError(); + }); + + expect( + doTaskEither.run, throwsA(const TypeMatcher())); + }); + + test('should rethrow if Left is thrown inside Do', () { + final doTaskEither = TaskEither.Do(($) { + $(TaskEither.of(10)); + throw Left('Error'); + }); + + expect(doTaskEither.run, throwsA(const TypeMatcher())); + }); + + test('should no execute past the first Left', () async { + var mutable = 10; + final doTaskEitherLeft = TaskEither.Do(($) async { + final a = await $(TaskEither.of(10)); + final b = await $(TaskEither.left("Error")); + mutable += 10; + return a + b; + }); + + final runLeft = await doTaskEitherLeft.run(); + expect(mutable, 10); + runLeft.matchTestLeft((l) { + expect(l, "Error"); + }); + + final doTaskEitherRight = TaskEither.Do(($) async { + final a = await $(TaskEither.of(10)); + mutable += 10; + return a; + }); + + final runRight = await doTaskEitherRight.run(); + expect(mutable, 20); + runRight.matchTestRight((t) { + expect(t, 10); + }); + }); + }); + }); +} From 038b2a8ab0dc8d9b8e09ad4e0681224214388e37 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 25 May 2023 18:56:26 +0200 Subject: [PATCH 50/71] tests ReaderTaskEither --- .../fpdart/lib/src/reader_task_either.dart | 8 +- packages/fpdart/lib/src/typeclass/monad.dart | 8 +- .../test/src/reader_task_either_test.dart | 124 ++++++++++++------ 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart index 280389f2..7933d10f 100644 --- a/packages/fpdart/lib/src/reader_task_either.dart +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -109,17 +109,17 @@ final class ReaderTaskEither ReaderTaskEither map(C Function(R r) f) => ap(pure(f)); @override - ReaderTaskEither map2( + ReaderTaskEither map2( covariant ReaderTaskEither m1, - N1 Function(R p1, N1 p2) f, + N2 Function(R p1, N1 p2) f, ) => flatMap((b) => m1.map((c) => f(b, c))); @override - ReaderTaskEither map3( + ReaderTaskEither map3( covariant ReaderTaskEither m1, covariant ReaderTaskEither m2, - N2 Function(R p1, N1 p2, N2 p3) f, + N3 Function(R p1, N1 p2, N2 p3) f, ) => flatMap( (b) => m1.flatMap((c) => m2.map((d) => f(b, c, d))), diff --git a/packages/fpdart/lib/src/typeclass/monad.dart b/packages/fpdart/lib/src/typeclass/monad.dart index 474a7548..a171bb8b 100644 --- a/packages/fpdart/lib/src/typeclass/monad.dart +++ b/packages/fpdart/lib/src/typeclass/monad.dart @@ -66,16 +66,16 @@ mixin Monad3 covariant Monad3 a) => a.flatMap((f) => flatMap((v) => pure(f(v)))); - HKT3 map2( + HKT3 map2( Monad3 m1, - N1 Function(P3, N1) f, + N2 Function(P3, N1) f, ) => flatMap((b) => m1.map((c) => f(b, c))); - HKT3 map3( + HKT3 map3( Monad3 m1, Monad3 m2, - N2 Function(P3, N1, N2) f, + N3 Function(P3, N1, N2) f, ) => flatMap( (b) => m1.flatMap((c) => m2.map((d) => f(b, c, d))), diff --git a/packages/fpdart/test/src/reader_task_either_test.dart b/packages/fpdart/test/src/reader_task_either_test.dart index f35b95d3..b7dbc27c 100644 --- a/packages/fpdart/test/src/reader_task_either_test.dart +++ b/packages/fpdart/test/src/reader_task_either_test.dart @@ -191,70 +191,108 @@ void main() { group('map2', () { test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ap = task.map2( - TaskEither(() async => Either.of(2)), (b, c) => b / c); - final r = await ap.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 5.0)); + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).map2( + ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ), + (b, c) => b / c, + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 1); + }); }); test('Left', () async { - final task = TaskEither(() async => Either.left('abc')); - final ap = task.map2( - TaskEither(() async => Either.of(2)), (b, c) => b / c); - final r = await ap.run(); - r.match((l) => expect(l, 'abc'), (_) { - fail('should be left'); + final apply = ReaderTaskEither( + (env) async => Either.left('$env'), + ).map2( + ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ), + (b, c) => b / c, + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); }); }); }); group('map3', () { test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ap = task.map3( - TaskEither(() async => Either.of(2)), - TaskEither(() async => Either.of(5)), - (b, c, d) => b * c / d); - final r = await ap.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 4.0)); + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).map3( + ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ), + ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ), + (b, c, d) => b * c / d, + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); + }); }); test('Left', () async { - final task = TaskEither(() async => Either.left('abc')); - final ap = task.map3( - TaskEither(() async => Either.of(2)), - TaskEither(() async => Either.of(5)), - (b, c, d) => b * c / d); - final r = await ap.run(); - r.match((l) => expect(l, 'abc'), (_) { - fail('should be left'); + final apply = ReaderTaskEither( + (env) async => Either.left('$env'), + ).map3( + ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ), + ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ), + (b, c, d) => b * c / d, + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); }); }); }); group('andThen', () { test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ap = task.andThen( - () => TaskEither(() async => Either.of(12.5))); - final r = await ap.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 12.5)); + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).andThen( + () => ReaderTaskEither( + (env) async => Either.of( + env.toInt() / 2, + ), + ), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 6); + }); }); test('Left', () async { - final task = TaskEither(() async => Either.left('abc')); - final ap = task.andThen( - () => TaskEither(() async => Either.of(12.5))); - final r = await ap.run(); - r.match((l) => expect(l, 'abc'), (_) { - fail('should be left'); + final apply = ReaderTaskEither( + (env) async => Either.left('$env'), + ).andThen( + () => ReaderTaskEither( + (env) async => Either.of(env.toInt() / 2), + ), + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); }); }); }); From 1e210abb4de0ce90c997a87b15f219d83cdd6e23 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 25 May 2023 19:29:55 +0200 Subject: [PATCH 51/71] tests ReaderTaskEither --- .../test/src/reader_task_either_test.dart | 846 ++++++------------ 1 file changed, 282 insertions(+), 564 deletions(-) diff --git a/packages/fpdart/test/src/reader_task_either_test.dart b/packages/fpdart/test/src/reader_task_either_test.dart index b7dbc27c..2ea89eb1 100644 --- a/packages/fpdart/test/src/reader_task_either_test.dart +++ b/packages/fpdart/test/src/reader_task_either_test.dart @@ -299,711 +299,425 @@ void main() { group('call', () { test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ap = - task(TaskEither(() async => Either.of(12.5))); - final r = await ap.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 12.5)); + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + )( + ReaderTaskEither( + (env) async => Either.of(env.toInt() / 2), + ), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 6); + }); }); test('Left', () async { - final task = TaskEither(() async => Either.left('abc')); - final ap = - task(TaskEither(() async => Either.of(12.5))); - final r = await ap.run(); - r.match((r) { - expect(r, 'abc'); - }, (_) { - fail('should be left'); + final apply = ReaderTaskEither( + (env) async => Either.left('$env'), + )( + ReaderTaskEither( + (env) async => Either.of(env.toInt() / 2), + ), + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); }); }); }); group('filterOrElse', () { test('Right (true)', () async { - final task = TaskEither(() async => Either.of(10)); - final ap = task.filterOrElse((r) => r > 5, (r) => 'abc'); - final r = await ap.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).filterOrElse( + (r) => r > 5, + (r) => 'abc', + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); + }); }); test('Right (false)', () async { - final task = TaskEither(() async => Either.of(10)); - final ap = task.filterOrElse((r) => r < 5, (r) => 'none'); - final r = await ap.run(); - r.match((l) => expect(l, 'none'), (_) { - fail('should be left'); + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).filterOrElse( + (r) => r < 5, + (r) => '$r', + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12"); }); }); test('Left', () async { - final task = TaskEither(() async => Either.left('abc')); - final ap = task.filterOrElse((r) => r > 5, (r) => 'none'); - final r = await ap.run(); - r.match((l) => expect(l, 'abc'), (_) { - fail('should be left'); + final apply = ReaderTaskEither( + (env) async => Either.left("$env"), + ).filterOrElse( + (r) => r > 5, + (r) => 'none', + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); }); }); }); test('pure', () async { - final task = TaskEither(() async => Either.left('abc')); - final ap = task.pure('abc'); - final r = await ap.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 'abc')); + final apply = ReaderTaskEither( + (env) async => Either.left("$env"), + ).pure('abc'); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, "abc"); + }); }); test('run', () async { - final task = TaskEither(() async => Either.of(10)); - final future = task.run(); + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ); + + final future = apply.run(12.2); expect(future, isA()); - final r = await future; - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); + final result = await future; + result.matchTestRight((r) { + expect(r, 12); + }); }); group('fromEither', () { test('Right', () async { - final task = TaskEither.fromEither(Either.of(10)); - final r = await task.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); + final apply = ReaderTaskEither.fromEither( + Either.of(10), + ); + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); + }); }); test('Left', () async { - final task = TaskEither.fromEither(Either.left('error')); - final r = await task.run(); - r.match((l) => expect(l, 'error'), (_) { - fail('should be left'); + final apply = ReaderTaskEither.fromEither( + Either.left('error'), + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "error"); }); }); }); group('fromOption', () { test('Right', () async { - final task = - TaskEither.fromOption(Option.of(10), () => 'none'); - final r = await task.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); + final apply = ReaderTaskEither.fromOption( + Option.of(10), + () => 'none', + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); + }); }); test('Left', () async { - final task = - TaskEither.fromOption(Option.none(), () => 'none'); - final r = await task.run(); - r.match((l) => expect(l, 'none'), (_) { - fail('should be left'); + final apply = ReaderTaskEither.fromOption( + Option.none(), + () => 'none', + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "none"); }); }); }); group('fromNullable', () { test('Right', () async { - final task = TaskEither.fromNullable(10, () => "Error"); - final result = await task.run(); + final apply = ReaderTaskEither.fromNullable( + 10, + () => "Error", + ); + + final result = await apply.run(12.2); result.matchTestRight((r) { expect(r, 10); }); }); test('Left', () async { - final task = TaskEither.fromNullable(null, () => "Error"); - final result = await task.run(); + final apply = ReaderTaskEither.fromNullable( + null, + () => "error", + ); + + final result = await apply.run(12.2); result.matchTestLeft((l) { - expect(l, "Error"); + expect(l, "error"); }); }); }); group('fromNullableAsync', () { test('Right', () async { - final task = TaskEither.fromNullableAsync( - 10, Task(() async => "Error")); - final result = await task.run(); + final apply = ReaderTaskEither.fromNullableAsync( + 10, + Task( + () async => "Error", + ), + ); + + final result = await apply.run(12.2); result.matchTestRight((r) { expect(r, 10); }); }); test('Left', () async { - final task = TaskEither.fromNullableAsync( - null, Task(() async => "Error")); - final result = await task.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - }); - }); - - group('fromPredicate', () { - test('True', () async { - final task = TaskEither.fromPredicate( - 20, (n) => n > 10, (n) => '$n'); - final r = await task.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 20)); - }); + final apply = ReaderTaskEither.fromNullableAsync( + null, + Task( + () async => "error", + ), + ); - test('False', () async { - final task = TaskEither.fromPredicate( - 10, (n) => n > 10, (n) => '$n'); - final r = await task.run(); - r.match((l) => expect(l, '10'), (_) { - fail('should be left'); + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "error"); }); }); }); test('fromTask', () async { - final task = TaskEither.fromTask(Task(() async => 10)); - final r = await task.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); - }); - - test('left', () async { - final task = TaskEither.left('none'); - final r = await task.run(); - r.match((l) => expect(l, 'none'), (_) { - fail('should be left'); - }); - }); - - test('right', () async { - final task = TaskEither.right(10); - final r = await task.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); - }); - - test('leftTask', () async { - final task = TaskEither.leftTask(Task(() async => 'none')); - final r = await task.run(); - r.match((l) => expect(l, 'none'), (_) { - fail('should be left'); - }); - }); - - test('rightTask', () async { - final task = TaskEither.rightTask(Task.of(10)); - final r = await task.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); - }); - - group('match', () { - test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ex = task.match((l) => l.length, (r) => r + 10); - final r = await ex.run(); - expect(r, 20); - }); - - test('Left', () async { - final task = TaskEither(() async => Either.left('none')); - final ex = task.match((l) => l.length, (r) => r + 10); - final r = await ex.run(); - expect(r, 4); - }); - }); + final apply = ReaderTaskEither.fromTask( + Task( + () async => 10, + ), + ); - group('getOrElse', () { - test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ex = task.getOrElse((l) => l.length); - final r = await ex.run(); + final result = await apply.run(12.2); + result.matchTestRight((r) { expect(r, 10); }); - - test('Left', () async { - final task = TaskEither(() async => Either.left('none')); - final ex = task.getOrElse((l) => l.length); - final r = await ex.run(); - expect(r, 4); - }); }); - group('orElse', () { - test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ex = - task.orElse((l) => TaskEither(() async => Right(l.length))); - final r = await ex.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); - }); - - test('Left', () async { - final task = TaskEither(() async => Either.left('none')); - final ex = - task.orElse((l) => TaskEither(() async => Right(l.length))); - final r = await ex.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 4)); - }); - }); - - group('alt', () { - test('Right', () async { - final task = TaskEither(() async => Either.of(10)); - final ex = task.alt(() => TaskEither(() async => Either.of(20))); - final r = await ex.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); - }); - - test('Left', () async { - final task = TaskEither(() async => Either.left('none')); - final ex = task.alt(() => TaskEither(() async => Either.of(20))); - final r = await ex.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 20)); - }); - }); + test('left', () async { + final apply = ReaderTaskEither.left( + 'none', + ); - test('swap', () async { - final task = TaskEither(() async => Either.of(10)); - final ex = task.swap(); - final r = await ex.run(); - r.match((l) => expect(l, 10), (_) { - fail('should be left'); + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, 'none'); }); }); - test('of', () async { - final task = TaskEither.of(10); - final r = await task.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); - }); - - test('flatten', () async { - final task = TaskEither>.of( - TaskEither.of(10)); - final ap = TaskEither.flatten(task); - final r = await ap.run(); - r.match((_) { - fail('should be right'); - }, (r) => expect(r, 10)); - }); - - test('chainFirst', () async { - final task = TaskEither.of(10); - var sideEffect = 10; - final chain = task.chainFirst((b) { - sideEffect = 100; - return TaskEither.left("abc"); - }); - final r = await chain.run(); - r.match( - (l) => fail('should be right'), - (r) { - expect(r, 10); - expect(sideEffect, 100); - }, + test('leftTask', () async { + final apply = ReaderTaskEither.leftTask( + Task( + () async => 'none', + ), ); - }); - - test('delay', () async { - final task = TaskEither(() async => Either.of(10)); - final ap = task.delay(const Duration(seconds: 2)); - final stopwatch = Stopwatch(); - stopwatch.start(); - await ap.run(); - stopwatch.stop(); - expect(stopwatch.elapsedMilliseconds >= 2000, true); - }); - - group('sequenceList', () { - test('Right', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right(1); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right(2); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right(3); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right(4); - }), - ]; - final traverse = TaskEither.sequenceList(list); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, list.length); - }); - test('Left', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right(1); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return left("Error"); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right(3); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right(4); - }), - ]; - final traverse = TaskEither.sequenceList(list); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, list.length); + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, 'none'); }); }); - group('traverseList', () { + group('orElse', () { test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = TaskEither.traverseList( - list, - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right("$a"); - }, + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).orElse( + (l) => ReaderTaskEither( + (env) async => Right(l.length), ), ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); }); - expect(sideEffect, list.length); }); test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = TaskEither.traverseList( - list, - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return a % 2 == 0 - ? right("$a") - : left("Error"); - }, + final apply = ReaderTaskEither( + (env) async => Either.left('$env'), + ).orElse( + (l) => ReaderTaskEither( + (env) async => Right(l.length), ), ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 4); }); - expect(sideEffect, list.length); }); }); - group('traverseListWithIndex', () { + group('alt', () { test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = TaskEither.traverseListWithIndex( - list, - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return right("$a$i"); - }, + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).alt( + () => ReaderTaskEither( + (env) async => Either.of(env.toInt() * 2), ), ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); }); - expect(sideEffect, list.length); }); test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = TaskEither.traverseListWithIndex( - list, - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect += 1; - return a % 2 == 0 - ? right("$a$i") - : left("Error"); - }, + final apply = ReaderTaskEither( + (env) async => Either.left('none'), + ).alt( + () => ReaderTaskEither( + (env) async => Either.of(env.toInt() * 12), ), ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 144); }); - expect(sideEffect, list.length); }); }); - group('sequenceListSeq', () { - test('Right', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 0; - return right(1); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 1; - return right(2); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 2; - return right(3); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 3; - return right(4); - }), - ]; - final traverse = TaskEither.sequenceListSeq(list); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, [1, 2, 3, 4]); - }); - expect(sideEffect, 3); - }); + test('swap', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).swap(); - test('Left', () async { - var sideEffect = 0; - final list = [ - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 0; - return right(1); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 1; - return left("Error"); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 2; - return right(3); - }), - TaskEither(() async { - await AsyncUtils.waitFuture(); - sideEffect = 3; - return right(4); - }), - ]; - final traverse = TaskEither.sequenceListSeq(list); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, 3); + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, 12); }); }); - group('traverseListSeq', () { - test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = TaskEither.traverseListSeq( - list, - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a - 1; - return right("$a"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['1', '2', '3', '4', '5', '6']); - }); - expect(sideEffect, 5); + test('of', () async { + final apply = ReaderTaskEither.of(10); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); }); + }); - test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = TaskEither.traverseListSeq( - list, - (a) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a - 1; - return a % 2 == 0 - ? right("$a") - : left("Error"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, 5); + test('flatten', () async { + final apply = ReaderTaskEither>.of( + ReaderTaskEither.of(10), + ); + + final result = await ReaderTaskEither.flatten(apply).run(12.2); + result.matchTestRight((r) { + expect(r, 10); }); }); - group('traverseListWithIndexSeq', () { - test('Right', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = - TaskEither.traverseListWithIndexSeq( - list, - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a + i; - return right("$a$i"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestRight((t) { - expect(t, ['10', '21', '32', '43', '54', '65']); - }); - expect(sideEffect, 11); + test('chainFirst', () async { + final apply = ReaderTaskEither.of(10); + var sideEffect = 10; + + final chain = apply.chainFirst((b) { + sideEffect = 100; + return ReaderTaskEither.left("$b"); }); - test('Left', () async { - final list = [1, 2, 3, 4, 5, 6]; - var sideEffect = 0; - final traverse = - TaskEither.traverseListWithIndexSeq( - list, - (a, i) => TaskEither( - () async { - await AsyncUtils.waitFuture(); - sideEffect = a + i; - return a % 2 == 0 - ? right("$a$i") - : left("Error"); - }, - ), - ); - expect(sideEffect, 0); - final result = await traverse.run(); - result.matchTestLeft((l) { - expect(l, "Error"); - }); - expect(sideEffect, 11); + expect(sideEffect, 10); + final result = await chain.run(12.2); + result.matchTestLeft((l) { + expect(l, "10"); + expect(sideEffect, 100); }); }); group('Do Notation', () { test('should return the correct value', () async { - final doTaskEither = - TaskEither.Do(($) => $(TaskEither.of(10))); - final run = await doTaskEither.run(); - run.matchTestRight((t) { - expect(t, 10); + final doTaskEither = ReaderTaskEither.Do( + ($) => $( + ReaderTaskEither.asks((env) => env.toInt()), + ), + ); + + final run = await doTaskEither.run(12.2); + run.matchTestRight((r) { + expect(r, 12); }); }); test('should extract the correct values', () async { - final doTaskEither = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); - final b = await $(TaskEither.of(5)); + final doTaskEither = + ReaderTaskEither.Do(($) async { + final a = await $(ReaderTaskEither.of(10)); + final b = await $(ReaderTaskEither.asks((env) => env.toInt())); return a + b; }); - final run = await doTaskEither.run(); - run.matchTestRight((t) { - expect(t, 15); + + final run = await doTaskEither.run(12.2); + run.matchTestRight((r) { + expect(r, 22); }); }); test('should return Left if any Either is Left', () async { - final doTaskEither = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); - final b = await $(TaskEither.of(5)); - final c = await $(TaskEither.left('Error')); + final doTaskEither = + ReaderTaskEither.Do(($) async { + final a = await $(ReaderTaskEither.of(10)); + final b = await $(ReaderTaskEither.asks((env) => env.toInt())); + final c = await $( + ReaderTaskEither.left('error'), + ); + return a + b + c; }); - final run = await doTaskEither.run(); - run.matchTestLeft((t) { - expect(t, 'Error'); + + final run = await doTaskEither.run(12.2); + run.matchTestLeft((l) { + expect(l, 'error'); }); }); test('should rethrow if throw is used inside Do', () { - final doTaskEither = TaskEither.Do(($) { - $(TaskEither.of(10)); + final doTaskEither = ReaderTaskEither.Do(($) { + $(ReaderTaskEither.of(10)); throw UnimplementedError(); }); expect( - doTaskEither.run, throwsA(const TypeMatcher())); + doTaskEither.run, + throwsA( + const TypeMatcher(), + ), + ); }); test('should rethrow if Left is thrown inside Do', () { - final doTaskEither = TaskEither.Do(($) { - $(TaskEither.of(10)); - throw Left('Error'); + final doTaskEither = ReaderTaskEither.Do(($) { + $( + ReaderTaskEither.of(10), + ); + throw Left('error'); }); expect(doTaskEither.run, throwsA(const TypeMatcher())); @@ -1011,29 +725,33 @@ void main() { test('should no execute past the first Left', () async { var mutable = 10; - final doTaskEitherLeft = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); - final b = await $(TaskEither.left("Error")); + + final doTaskEitherLeft = + ReaderTaskEither.Do(($) async { + final a = await $(ReaderTaskEither.of(10)); + final b = + await $(ReaderTaskEither.left("error")); mutable += 10; return a + b; }); - final runLeft = await doTaskEitherLeft.run(); + final runLeft = await doTaskEitherLeft.run(12.2); expect(mutable, 10); runLeft.matchTestLeft((l) { - expect(l, "Error"); + expect(l, "error"); }); - final doTaskEitherRight = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); + final doTaskEitherRight = + ReaderTaskEither.Do(($) async { + final a = await $(ReaderTaskEither.asks((env) => env.toInt())); mutable += 10; return a; }); - final runRight = await doTaskEitherRight.run(); + final runRight = await doTaskEitherRight.run(12.2); expect(mutable, 20); - runRight.matchTestRight((t) { - expect(t, 10); + runRight.matchTestRight((r) { + expect(r, 12); }); }); }); From 4559a5d613fb2669aaf3ad9e883add7ef016a58a Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 25 May 2023 19:34:38 +0200 Subject: [PATCH 52/71] complete tests ReaderTaskEither --- .../test/src/reader_task_either_test.dart | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/fpdart/test/src/reader_task_either_test.dart b/packages/fpdart/test/src/reader_task_either_test.dart index 2ea89eb1..ec731073 100644 --- a/packages/fpdart/test/src/reader_task_either_test.dart +++ b/packages/fpdart/test/src/reader_task_either_test.dart @@ -4,6 +4,26 @@ import './utils/utils.dart'; void main() { group('ReaderTaskEither', () { + test('ask', () async { + final apply = ReaderTaskEither.ask(); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12.2); + }); + }); + + test('asks', () async { + final apply = ReaderTaskEither.asks( + (env) => env.toInt(), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); + }); + }); + group('tryCatch', () { test('Success', () async { final apply = ReaderTaskEither.tryCatch( @@ -705,7 +725,7 @@ void main() { }); expect( - doTaskEither.run, + () => doTaskEither.run(12.2), throwsA( const TypeMatcher(), ), @@ -720,7 +740,12 @@ void main() { throw Left('error'); }); - expect(doTaskEither.run, throwsA(const TypeMatcher())); + expect( + () => doTaskEither.run(12.2), + throwsA( + const TypeMatcher(), + ), + ); }); test('should no execute past the first Left', () async { From 0d3328d5f686b6204ce477f36ac46834eec172d2 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 25 May 2023 19:55:25 +0200 Subject: [PATCH 53/71] ReaderTaskEither conversions --- .../src/reader_task_either/overview.dart | 2 +- .../fpdart/lib/src/reader_task_either.dart | 46 +++++++- .../test/src/reader_task_either_test.dart | 109 ++++++++++++++++++ 3 files changed, 152 insertions(+), 5 deletions(-) diff --git a/packages/fpdart/example/src/reader_task_either/overview.dart b/packages/fpdart/example/src/reader_task_either/overview.dart index b95845fd..859669bd 100644 --- a/packages/fpdart/example/src/reader_task_either/overview.dart +++ b/packages/fpdart/example/src/reader_task_either/overview.dart @@ -7,7 +7,7 @@ typedef Success = String; void main(List args) async { final rte = ReaderTaskEither.Do(($) async { final a = 10; - final val = await $(ReaderTaskEither.rightReader( + final val = await $(ReaderTaskEither.fromReader( Reader( (env) => env.$1 + env.$2.length, ), diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart index 7933d10f..2c2618d2 100644 --- a/packages/fpdart/lib/src/reader_task_either.dart +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -1,9 +1,13 @@ import 'either.dart'; import 'function.dart'; +import 'io.dart'; +import 'io_either.dart'; +import 'io_option.dart'; import 'option.dart'; import 'reader.dart'; import 'task.dart'; import 'task_either.dart'; +import 'task_option.dart'; import 'typeclass/alt.dart'; import 'typeclass/applicative.dart'; import 'typeclass/functor.dart'; @@ -213,8 +217,8 @@ final class ReaderTaskEither ) => readerTaskEither.flatMap(identity); - /// Build a [ReaderTaskEither] from a `Reader`. - factory ReaderTaskEither.rightReader(Reader reader) => ReaderTaskEither( + /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `reader`. + factory ReaderTaskEither.fromReader(Reader reader) => ReaderTaskEither( (env) async => Right(reader.run(env)), ); @@ -234,12 +238,46 @@ final class ReaderTaskEither ); /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `task`. - /// - /// Same of `ReaderTaskEither.fromTask` factory ReaderTaskEither.fromTask(Task task) => ReaderTaskEither( (_) async => Right(await task.run()), ); + /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `task`, + /// or the result of `onNone` if `task` is [Left]. + factory ReaderTaskEither.fromTaskOption( + TaskOption task, + L Function() onNone, + ) => + ReaderTaskEither( + (_) async => Either.fromOption(await task.run(), onNone), + ); + + /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `task`. + factory ReaderTaskEither.fromTaskEither(TaskEither task) => + ReaderTaskEither( + (_) async => task.run(), + ); + + /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `io`. + factory ReaderTaskEither.fromIO(IO io) => ReaderTaskEither( + (_) async => Right(io.run()), + ); + + /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `io`, + /// or the result of `onNone` if `io` is [Left]. + factory ReaderTaskEither.fromIOOption( + IOOption io, + L Function() onNone, + ) => + ReaderTaskEither( + (_) async => Either.fromOption(io.run(), onNone), + ); + + /// Build a [ReaderTaskEither] that returns a [Right] containing the result of running `io`. + factory ReaderTaskEither.fromIOEither(IOEither io) => ReaderTaskEither( + (_) async => io.run(), + ); + /// {@template fpdart_from_nullable_reader_task_either} /// If `r` is `null`, then return the result of `onNull` in [Left]. /// Otherwise return `Right(r)`. diff --git a/packages/fpdart/test/src/reader_task_either_test.dart b/packages/fpdart/test/src/reader_task_either_test.dart index ec731073..c101d4f0 100644 --- a/packages/fpdart/test/src/reader_task_either_test.dart +++ b/packages/fpdart/test/src/reader_task_either_test.dart @@ -535,6 +535,115 @@ void main() { }); }); + group('fromIOOption', () { + test('Right', () async { + final apply = ReaderTaskEither.fromIOOption( + IOOption.of(10), + () => "none", + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('Left', () async { + final apply = ReaderTaskEither.fromIOOption( + IOOption.none(), + () => "none", + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "none"); + }); + }); + }); + + group('fromTaskOption', () { + test('Right', () async { + final apply = ReaderTaskEither.fromTaskOption( + TaskOption.of(10), + () => "none", + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('Left', () async { + final apply = ReaderTaskEither.fromTaskOption( + TaskOption.none(), + () => "none", + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "none"); + }); + }); + }); + + test('fromTaskEither', () async { + final apply = ReaderTaskEither.fromTaskEither( + TaskEither.of(10), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('fromIO', () async { + final apply = ReaderTaskEither.fromIO( + IO( + () => 10, + ), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('fromIOEither', () async { + final apply = ReaderTaskEither.fromIOEither( + IOEither.of(10), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('fromReader', () async { + final apply = ReaderTaskEither.fromReader( + Reader((env) => env.toInt()), + ); + + final result = await apply.run(12.2); + result.matchTestRight((r) { + expect(r, 12); + }); + }); + + test('leftReader', () async { + final apply = ReaderTaskEither.leftReader( + Reader((env) => "$env"), + ); + + final result = await apply.run(12.2); + result.matchTestLeft((l) { + expect(l, "12.2"); + }); + }); + test('left', () async { final apply = ReaderTaskEither.left( 'none', From 35d9392d4403af4319fd87b0dc7ed46fb0525456 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 25 May 2023 20:02:10 +0200 Subject: [PATCH 54/71] updated CHANGELOG --- packages/fpdart/CHANGELOG.md | 15 +++++++++++---- packages/fpdart/lib/src/reader_task_either.dart | 8 +++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index bcb7c0a5..1da3c9a2 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -1,9 +1,16 @@ ## v1.0.0 - Soon -- `Either` as `sealed` class +- Minimum environment dart sdk to `3.0.0` ⚠️ (Dart 3️⃣) +```yaml +environment: + sdk: ">=3.0.0 <4.0.0" +``` +- Added new `ReaderTaskEither` type + - `ReaderTaskEither` models a complete program using `Reader` for dependency injection, `Task` to perform asynchronous computation, and `Either` to handle errors 🎯 +- `Either` as `sealed` class (Dart 3️⃣) - You can now use exhaustive pattern matching (`Left` or `Right`) -- `Option` as `sealed` class +- `Option` as `sealed` class (Dart 3️⃣) - You can now use exhaustive pattern matching (`None` or `Some`) -- Types marked as `final` (no `extends` nor `implements`) +- Types marked as `final` (no `extends` nor `implements`) (Dart 3️⃣) - `Unit` - `Reader` - `State` @@ -15,7 +22,7 @@ - `Task` - `TaskOption` - `TaskEither` -- Removed `Tuple2`, use Dart 3 Records instead (`Tuple2(a, b)` becomes simply `(a, b)` 🎯) ⚠️ +- Removed `Tuple2`, use Dart 3 Records instead (`Tuple2(a, b)` becomes simply `(a, b)` 🎯) ⚠️ (Dart 3️⃣) - Updated all internal APIs to use records instead of `Tuple2` - Major refactoring of `Iterable` and `List` extension methods - Improved performance diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart index 2c2618d2..7359cd1d 100644 --- a/packages/fpdart/lib/src/reader_task_either.dart +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -32,10 +32,12 @@ typedef DoFunctionReaderTaskEither = Future Function( /// Tag the [HKT3] interface for the actual [ReaderTaskEither]. abstract final class _ReaderTaskEitherHKT {} -/// `ReaderTaskEither` represents an asynchronous computation that -/// either yields a value of type `R` or fails yielding an error of type `L`. +/// `ReaderTaskEither` represents an asynchronous computation (`Task`) that +/// either yields a value of type `R` or fails yielding an error of type `L` (`Either`), +/// that allows to read values from a dependency/context `E` (`Reader`). /// -/// If you want to represent an asynchronous computation that never fails, see [Task]. +/// [ReaderTaskEither] models a complete program using `Reader` for dependency injection, +/// `Task` to perform asynchronous computation, and `Either` to handle errors. final class ReaderTaskEither extends HKT3<_ReaderTaskEitherHKT, E, L, R> with From 1415d340d99676fce75e4ec2ec9b4f31575d3a6f Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 15:24:06 +0200 Subject: [PATCH 55/71] ReaderTask --- packages/fpdart/lib/src/reader_task.dart | 135 ++++++++++++++++++ .../fpdart/lib/src/reader_task_either.dart | 1 + 2 files changed, 136 insertions(+) create mode 100644 packages/fpdart/lib/src/reader_task.dart diff --git a/packages/fpdart/lib/src/reader_task.dart b/packages/fpdart/lib/src/reader_task.dart new file mode 100644 index 00000000..6e2565ef --- /dev/null +++ b/packages/fpdart/lib/src/reader_task.dart @@ -0,0 +1,135 @@ +import 'either.dart'; +import 'function.dart'; +import 'reader_task_either.dart'; +import 'typeclass/applicative.dart'; +import 'typeclass/functor.dart'; +import 'typeclass/hkt.dart'; +import 'typeclass/monad.dart'; + +typedef DoAdapterReaderTask = Future Function(ReaderTask); +DoAdapterReaderTask _doAdapter(E env) => + (ReaderTask task) => task.run(env); + +typedef DoFunctionReaderTask = Future Function( + DoAdapterReaderTask $); + +/// Tag the [HKT] interface for the actual [ReaderTask]. +abstract final class _ReaderTaskHKT {} + +/// [ReaderTask] represents an asynchronous computation that yields a value of type `A` +/// from a context of type `E` and **never fails**. +/// +/// If you want to represent an asynchronous computation that may fail, see [ReaderTaskEither]. +final class ReaderTask extends HKT2<_ReaderTaskHKT, E, A> + with + Functor2<_ReaderTaskHKT, E, A>, + Applicative2<_ReaderTaskHKT, E, A>, + Monad2<_ReaderTaskHKT, E, A> { + final Future Function(E) _run; + + /// Build a [ReaderTask] from a function returning a [Future] given `E`. + const ReaderTask(this._run); + + /// Initialize a **Do Notation** chain. + // ignore: non_constant_identifier_names + factory ReaderTask.Do(DoFunctionReaderTask f) => + ReaderTask((env) => f(_doAdapter(env))); + + /// Build a [ReaderTask] that returns `a`. + factory ReaderTask.of(A a) => ReaderTask((_) async => a); + + /// Flat a [ReaderTask] contained inside another [ReaderTask] to be a single [ReaderTask]. + factory ReaderTask.flatten(ReaderTask> task) => + task.flatMap(identity); + + /// Apply the function contained inside `a` to change the value of type `A` to + /// a value of type `B`. + @override + ReaderTask ap(covariant ReaderTask a) => + ReaderTask( + (env) => a.run(env).then( + (f) => run(env).then( + (v) => f(v), + ), + ), + ); + + /// Used to chain multiple functions that return a [ReaderTask]. + /// + /// You can extract the value inside the [ReaderTask] without actually running it. + @override + ReaderTask flatMap(covariant ReaderTask Function(A a) f) => + ReaderTask( + (env) => run(env).then( + (v) => f(v).run(env), + ), + ); + + /// Return a [ReaderTask] returning the value `b`. + @override + ReaderTask pure(B a) => ReaderTask((_) async => a); + + /// Change the returning value of the [ReaderTask] from type + /// `A` to type `B` using `f`. + @override + ReaderTask map(B Function(A a) f) => ap(pure(f)); + + /// Change type of this [ReaderTask] based on its value of type `A` and the + /// value of type `C` of another [ReaderTask]. + @override + ReaderTask map2( + covariant ReaderTask mc, D Function(A a, C c) f) => + flatMap( + (a) => mc.map( + (c) => f(a, c), + ), + ); + + /// Change type of this [ReaderTask] based on its value of type `A`, the + /// value of type `C` of a second [ReaderTask], and the value of type `D` + /// of a third [ReaderTask]. + @override + ReaderTask map3( + covariant ReaderTask mc, + covariant ReaderTask md, + F Function(A a, C c, D d) f, + ) => + flatMap( + (a) => mc.flatMap( + (c) => md.map( + (d) => f(a, c, d), + ), + ), + ); + + /// Run this [ReaderTask] and right after the [ReaderTask] returned from `then`. + @override + ReaderTask andThen(covariant ReaderTask Function() then) => + flatMap( + (_) => then(), + ); + + @override + ReaderTask chainFirst( + covariant ReaderTask Function(A a) chain, + ) => + flatMap( + (a) => chain(a).map((b) => a), + ); + + /// Chain multiple [ReaderTask] functions. + @override + ReaderTask call(covariant ReaderTask chain) => flatMap( + (_) => chain, + ); + + /// Run the task and return a [Future]. + Future run(E env) => _run(env); + + /// Convert this [ReaderTask] to [ReaderTaskEither]. + ReaderTaskEither toTaskReaderEither() => ReaderTaskEither( + (env) async => Either.of( + await run(env), + ), + ); +} diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart index 7359cd1d..23f153e3 100644 --- a/packages/fpdart/lib/src/reader_task_either.dart +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -21,6 +21,7 @@ final class _ReaderTaskEitherThrow { typedef DoAdapterReaderTaskEither = Future Function( ReaderTaskEither); + DoAdapterReaderTaskEither _doAdapter(E env) => (readerTaskEither) => readerTaskEither.run(env).then( (either) => either.getOrElse((l) => throw _ReaderTaskEitherThrow(l)), From 6294c6161b578a2fef55118946e8f237f454ed0b Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 15:45:22 +0200 Subject: [PATCH 56/71] ReaderTask tests --- packages/fpdart/lib/fpdart.dart | 1 + packages/fpdart/lib/src/reader_task.dart | 2 +- .../fpdart/test/src/reader_task_test.dart | 212 ++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 packages/fpdart/test/src/reader_task_test.dart diff --git a/packages/fpdart/lib/fpdart.dart b/packages/fpdart/lib/fpdart.dart index b3a6d154..a3d852b7 100644 --- a/packages/fpdart/lib/fpdart.dart +++ b/packages/fpdart/lib/fpdart.dart @@ -9,6 +9,7 @@ export 'src/io_ref.dart'; export 'src/option.dart'; export 'src/random.dart'; export 'src/reader.dart'; +export 'src/reader_task.dart'; export 'src/reader_task_either.dart'; export 'src/state.dart'; export 'src/state_async.dart'; diff --git a/packages/fpdart/lib/src/reader_task.dart b/packages/fpdart/lib/src/reader_task.dart index 6e2565ef..200ce3a7 100644 --- a/packages/fpdart/lib/src/reader_task.dart +++ b/packages/fpdart/lib/src/reader_task.dart @@ -127,7 +127,7 @@ final class ReaderTask extends HKT2<_ReaderTaskHKT, E, A> Future run(E env) => _run(env); /// Convert this [ReaderTask] to [ReaderTaskEither]. - ReaderTaskEither toTaskReaderEither() => ReaderTaskEither( + ReaderTaskEither toReaderTaskEither() => ReaderTaskEither( (env) async => Either.of( await run(env), ), diff --git a/packages/fpdart/test/src/reader_task_test.dart b/packages/fpdart/test/src/reader_task_test.dart new file mode 100644 index 00000000..90fe7878 --- /dev/null +++ b/packages/fpdart/test/src/reader_task_test.dart @@ -0,0 +1,212 @@ +import 'package:fpdart/fpdart.dart'; + +import 'utils/utils.dart'; + +void main() { + group('ReaderTask', () { + group('map', () { + test('int to int', () async { + final apply = ReaderTask( + (env) async => env.length, + ).map((a) => a + 1); + + final result = await apply.run("abc"); + expect(result, 4); + }); + }); + + test('ap', () async { + final apply = ReaderTask( + (env) async => env.length, + ).ap( + ReaderTask( + (env) async => (a) => a + 1, + ), + ); + + final result = await apply.run("abc"); + expect(result, 4); + }); + + test('flatMap', () async { + final apply = ReaderTask( + (env) async => env.length, + ) + .flatMap( + (a) => ReaderTask( + (env) async => '$a-$env', + ), + ) + .flatMap( + (a) => ReaderTask( + (env) async => a.length + env.length, + ), + ); + + final result = await apply.run("abc"); + expect(result, 8); + }); + + test('pure', () async { + final apply = ReaderTask( + (env) async => env.length, + ).pure('abc'); + + final result = await apply.run("abc"); + expect(result, 'abc'); + }); + + test('map2', () async { + final apply = ReaderTask( + (env) async => env.length, + ).map2( + ReaderTask.of('abc'), + (a, c) => a + c.length, + ); + + final result = await apply.run("abc"); + expect(result, 6); + }); + + test('map3', () async { + final apply = ReaderTask( + (env) async => env.length, + ).map3( + ReaderTask.of(2), + ReaderTask.of('abc'), + (a, c, d) => (a + d.length) / c, + ); + + final result = await apply.run("abc"); + expect(result, 3); + }); + + test('of', () async { + final apply = ReaderTask.of(10); + + final result = await apply.run("abc"); + expect(result, 10); + }); + + test('run', () async { + final apply = ReaderTask.of(10); + final future = apply.run("abc"); + + expect(future, isA>()); + final result = await future; + expect(result, 10); + }); + + test('flatten', () async { + final apply = ReaderTask>.of( + ReaderTask.of(10), + ); + + final mid = await apply.run("abc"); + final flatten = ReaderTask.flatten(apply); + + final resultMid = await mid.run("abc"); + final resultFlatten = await flatten.run("abc"); + + expect(resultMid, 10); + expect(resultFlatten, 10); + expect(resultMid, resultFlatten); + }); + + group('andThen', () { + test('run a Task after another Task', () async { + final apply = ReaderTask( + (env) async => env.length, + ).andThen( + () => ReaderTask( + (env) async => env.length * 2, + ), + ); + + final result = await apply.run("abc"); + expect(result, 6); + }); + + test('never run the second Task since the first throws', () async { + final apply = ReaderTask( + (env) async => throw UnimplementedError(), + ); + + final result = apply.andThen( + () => ReaderTask.of(12.2), + ); + expect(() => result.run("abc"), + throwsA(const TypeMatcher())); + }); + }); + + group('call', () { + test('run a Task after another Task', () async { + final apply = ReaderTask( + (env) async => env.length, + )(ReaderTask.of('abc')); + + final result = await apply.run("abc"); + expect(result, 'abc'); + }); + + test('run the second Task but return throw error', () async { + final apply = + ReaderTask((env) async => throw UnimplementedError())( + ReaderTask.of('abc'), + ); + + expect(() => apply.run("abc"), + throwsA(const TypeMatcher())); + }); + }); + + test('toReaderTaskEither', () async { + final apply = ReaderTask.of(10).toReaderTaskEither(); + + final result = await apply.run("abc"); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + group('Do Notation', () { + test('should return the correct value', () async { + final doTask = ReaderTask.Do( + ($) => $( + ReaderTask.of(10), + ), + ); + + final run = await doTask.run("abc"); + expect(run, 10); + }); + + test('should extract the correct values', () async { + final doTask = ReaderTask.Do(($) async { + final a = await $(ReaderTask((env) async => env.length)); + final b = await $(ReaderTask((env) async => env.length)); + return a + b; + }); + + final run = await doTask.run("abc"); + expect(run, 6); + }); + + test('should not execute until run is called', () async { + var mutable = 10; + final doTask = ReaderTask.Do(($) async { + final a = await $(ReaderTask.of(10)); + final b = await $(ReaderTask.of(5)); + mutable += 10; + return a + b; + }); + + expect(mutable, 10); + final run = await doTask.run("abc"); + expect(mutable, 20); + expect(run, 15); + }); + }); + }); +} From 98046d4f5843ff17a33c10d8995a2e8673396e0e Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 16:06:36 +0200 Subject: [PATCH 57/71] ReaderTask and ReaderTaskEither --- packages/fpdart/lib/src/reader_task.dart | 12 ++++- .../fpdart/lib/src/reader_task_either.dart | 27 ++++++++++ .../test/src/reader_task_either_test.dart | 52 ++++++++++++++++++- .../fpdart/test/src/reader_task_test.dart | 16 ++++++ 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/packages/fpdart/lib/src/reader_task.dart b/packages/fpdart/lib/src/reader_task.dart index 200ce3a7..edd85cc3 100644 --- a/packages/fpdart/lib/src/reader_task.dart +++ b/packages/fpdart/lib/src/reader_task.dart @@ -25,7 +25,7 @@ final class ReaderTask extends HKT2<_ReaderTaskHKT, E, A> Functor2<_ReaderTaskHKT, E, A>, Applicative2<_ReaderTaskHKT, E, A>, Monad2<_ReaderTaskHKT, E, A> { - final Future Function(E) _run; + final Future Function(E env) _run; /// Build a [ReaderTask] from a function returning a [Future] given `E`. const ReaderTask(this._run); @@ -132,4 +132,14 @@ final class ReaderTask extends HKT2<_ReaderTaskHKT, E, A> await run(env), ), ); + + /// Extract a value `A` given the current dependency `E`. + factory ReaderTask.asks(A Function(E) f) => ReaderTask( + (env) async => f(env), + ); + + /// Read the current dependency `E`. + static ReaderTask ask() => ReaderTask( + (env) async => env, + ); } diff --git a/packages/fpdart/lib/src/reader_task_either.dart b/packages/fpdart/lib/src/reader_task_either.dart index 23f153e3..ab4fe51e 100644 --- a/packages/fpdart/lib/src/reader_task_either.dart +++ b/packages/fpdart/lib/src/reader_task_either.dart @@ -5,6 +5,7 @@ import 'io_either.dart'; import 'io_option.dart'; import 'option.dart'; import 'reader.dart'; +import 'reader_task.dart'; import 'task.dart'; import 'task_either.dart'; import 'task_option.dart'; @@ -214,6 +215,32 @@ final class ReaderTaskEither (r) => ReaderTaskEither.of(r).run(env), )); + /// Convert this [ReaderTaskEither] to a [ReaderTask]. + /// + /// The task returns a [Right] when [ReaderTaskEither] returns [Right]. + /// Otherwise map the type `L` of [ReaderTaskEither] to type `R` by calling `orElse`. + ReaderTask getOrElse(R Function(L left) orElse) => ReaderTask( + (env) async => (await run(env)).match( + orElse, + identity, + ), + ); + + /// Pattern matching to convert a [ReaderTaskEither] to a [ReaderTask]. + /// + /// Execute `onLeft` when running this [ReaderTaskEither] returns a [Left]. + /// Otherwise execute `onRight`. + ReaderTask match( + B Function(L left) onLeft, + B Function(R right) onRight, + ) => + ReaderTask( + (env) async => (await run(env)).match( + onLeft, + onRight, + ), + ); + /// Flat a [ReaderTaskEither] contained inside another [ReaderTaskEither] to be a single [ReaderTaskEither]. factory ReaderTaskEither.flatten( ReaderTaskEither> readerTaskEither, diff --git a/packages/fpdart/test/src/reader_task_either_test.dart b/packages/fpdart/test/src/reader_task_either_test.dart index c101d4f0..abbe55e2 100644 --- a/packages/fpdart/test/src/reader_task_either_test.dart +++ b/packages/fpdart/test/src/reader_task_either_test.dart @@ -5,7 +5,7 @@ import './utils/utils.dart'; void main() { group('ReaderTaskEither', () { test('ask', () async { - final apply = ReaderTaskEither.ask(); + final apply = ReaderTaskEither.ask(); final result = await apply.run(12.2); result.matchTestRight((r) { @@ -24,6 +24,56 @@ void main() { }); }); + group('getOrElse', () { + test('Right', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).getOrElse( + (left) => left.length, + ); + + final result = await apply.run(12.2); + expect(result, 12); + }); + + test('Left', () async { + final apply = ReaderTaskEither( + (env) async => Either.left(env.toString()), + ).getOrElse( + (left) => left.length, + ); + + final result = await apply.run(12.2); + expect(result, 4); + }); + }); + + group('match', () { + test('Right', () async { + final apply = ReaderTaskEither( + (env) async => Either.of(env.toInt()), + ).match( + (left) => left.length, + (right) => right + 10, + ); + + final result = await apply.run(12.2); + expect(result, 22); + }); + + test('Left', () async { + final apply = ReaderTaskEither( + (env) async => Either.left(env.toString()), + ).match( + (left) => left.length, + (right) => right + 10, + ); + + final result = await apply.run(12.2); + expect(result, 4); + }); + }); + group('tryCatch', () { test('Success', () async { final apply = ReaderTaskEither.tryCatch( diff --git a/packages/fpdart/test/src/reader_task_test.dart b/packages/fpdart/test/src/reader_task_test.dart index 90fe7878..8b95eb07 100644 --- a/packages/fpdart/test/src/reader_task_test.dart +++ b/packages/fpdart/test/src/reader_task_test.dart @@ -4,6 +4,22 @@ import 'utils/utils.dart'; void main() { group('ReaderTask', () { + test('ask', () async { + final apply = ReaderTask.ask(); + + final result = await apply.run("abc"); + expect(result, "abc"); + }); + + test('asks', () async { + final apply = ReaderTask.asks( + (env) => env.length, + ); + + final result = await apply.run("abc"); + expect(result, 3); + }); + group('map', () { test('int to int', () async { final apply = ReaderTask( From 55761d2fd6da09d3ed79c4b5aa2df04bb0a6129b Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 16:11:12 +0200 Subject: [PATCH 58/71] update README --- packages/fpdart/README.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index 0694a23f..17a4d93b 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -35,7 +35,7 @@ All the main functional programming types and patterns fully documented< Fpdart is inspired by [fp-ts](https://gcanti.github.io/fp-ts/), [cats](https://typelevel.org/cats/typeclasses.html#type-classes-in-cats), and [dartz](https://github.com/spebbe/dartz). -> **Note**: The API is still evolving and it may change. New documentation and testing is always ongoing. Follow my [**Twitter**](https://twitter.com/SandroMaglione) for updates, or [subscribe to the newsletter](https://www.sandromaglione.com/newsletter) +> Follow my [**Twitter**](https://twitter.com/SandroMaglione) for updates, or [subscribe to the newsletter](https://www.sandromaglione.com/newsletter) *** @@ -54,6 +54,8 @@ Fpdart is inspired by [fp-ts](https://gcanti.github.io/fp-ts/), [cats](https://t - [Task](#task) - [Utility types](#utility-types) - [Reader](#reader) + - [ReaderTask](#readertask) + - [ReaderTaskEither](#readertaskeither) - [State](#state) - [🔗 Do notation](#-do-notation) - [📦 Immutable Collections](#-immutable-collections) @@ -255,7 +257,13 @@ These types compose together the 4 above ([`Option`](#option), [`Either`](#eithe ### [Reader](./packages/fpdart/lib/src/reader.dart) Read values from a **context** without explicitly passing the dependency between multiple nested function calls. View the [example folder for an explained usecase example](./examples/src/reader). -### [State](./packages/fpdart/lib/src/state.dart) +#### [ReaderTask](./lib/src/reader_task.dart) +Combine the `Reader` type (dependecy) with `Task` (asynchronous). + +#### [ReaderTaskEither](./lib/src/reader_task_either.dart) +Combine the `Reader` type (dependecy) with `Task` (asynchronous) and `Either` (error handling). + +### [State](./lib/src/state.dart) Used to **store**, **update**, and **extract** state in a functional way. View the [example folder for an explained usecase example](./examples/src/state). ### 🔗 Do notation @@ -341,9 +349,9 @@ Many more examples are coming soon. Check out [**my website**](https://www.sandr - [x] `TaskOption` - [x] `Predicate` - [x] `IOOption` +- [x] `ReaderTask` +- [x] `ReaderTaskEither` - [ ] `ReaderEither` -- [ ] `ReaderTask` -- [ ] `ReaderTaskEither` - [ ] `StateReaderTaskEither` - [ ] `Lens` - [ ] `Writer` @@ -393,11 +401,9 @@ Being documentation and stability important goals of the package, every type wil The roadmap for types development is highlighted below (breaking changes to _'stable'_ types are to be expected in this early stages): 1. `ReaderEither` -2. `ReaderTask` -3. `ReaderTaskEither` -4. `StateReaderTaskEither` -5. `Writer` -6. `Lens` +2. `StateReaderTaskEither` +3. `Writer` +4. `Lens` > **Note**: There is also an experimental research in progress to implement [`ZIO`](https://zio.dev/) in `fpdart`, stay tuned 🔜 From 63933a8dfefbfa347ebb3d942fa2a89bb2f4d045 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 17:50:29 +0200 Subject: [PATCH 59/71] static members DateTime Order and Eq --- packages/fpdart/CHANGELOG.md | 22 ++++++--- packages/fpdart/example/src/map/overview.dart | 13 ++++-- .../fpdart/example/src/typeclass/eq/eq1.dart | 4 +- .../example/src/typeclass/order/order2.dart | 4 +- packages/fpdart/lib/src/date.dart | 30 ------------- .../src/extension/date_time_extension.dart | 11 ++--- .../lib/src/extension/iterable_extension.dart | 3 +- packages/fpdart/lib/src/typeclass/eq.dart | 37 ++++++++++++--- packages/fpdart/lib/src/typeclass/order.dart | 21 +++++---- packages/fpdart/test/src/date_test.dart | 43 +++++++----------- packages/fpdart/test/src/eq_test.dart | 14 +++--- .../extension/date_time_extension_test.dart | 8 ++-- .../extension/iterable_extension_test.dart | 4 +- .../src/extension/map_extension_test.dart | 45 ++++++++++--------- packages/fpdart/test/src/order_test.dart | 27 +++++++---- 15 files changed, 152 insertions(+), 134 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 1da3c9a2..05bb9280 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -6,6 +6,7 @@ environment: ``` - Added new `ReaderTaskEither` type - `ReaderTaskEither` models a complete program using `Reader` for dependency injection, `Task` to perform asynchronous computation, and `Either` to handle errors 🎯 +- Added new `ReaderTask` type - `Either` as `sealed` class (Dart 3️⃣) - You can now use exhaustive pattern matching (`Left` or `Right`) - `Option` as `sealed` class (Dart 3️⃣) @@ -22,6 +23,8 @@ environment: - `Task` - `TaskOption` - `TaskEither` + - `ReaderTask` + - `ReaderTaskEither` - Removed `Tuple2`, use Dart 3 Records instead (`Tuple2(a, b)` becomes simply `(a, b)` 🎯) ⚠️ (Dart 3️⃣) - Updated all internal APIs to use records instead of `Tuple2` - Major refactoring of `Iterable` and `List` extension methods @@ -152,9 +155,17 @@ final add = addFunction.curry; - `or` - `and` - Added `xor` method to `Eq` +- Moved `DateTime` instances of `Eq` as `Eq` static members +```dart +/// Before +final eq = dateEqYear; // Global + +/// New +final eq = Eq.dateEqYear; +``` - Added `Eq` instances for `num`, `int`, `double`, `String`, and `bool` ```dart -[1, 2, 3].difference(Eq.eqInt(), [2, 3, 4]); /// `[1]` +[1, 2, 3].difference(Eq.eqInt, [2, 3, 4]); /// `[1]` ``` - Added new method to `Eq` - `contramap` @@ -166,12 +177,12 @@ class Parent { } /// Equality for values of type [Parent] based on their `value1` ([int]). -final eqParentInt = Eq.eqInt().contramap( +final eqParentInt = Eq.eqInt.contramap( (p) => p.value1, ); /// Equality for of type [Parent] based on their `value2` ([double]). -final eqParentDouble = Eq.eqDouble().contramap( +final eqParentDouble = Eq.eqDouble.contramap( (p) => p.value2, ); ``` @@ -183,6 +194,7 @@ final reversed = Order.reverse(instance); /// Before final reversed = instance.reverse; ``` +- Moved `DateTime` instances of `Order` as `Order` static members - Added `Order` instances for `num`, `int`, `double` - Added new methods to `Order` - `between` @@ -196,12 +208,12 @@ class Parent { } /// Order values of type [Parent] based on their `value1` ([int]). -final orderParentInt = Order.orderInt().contramap( +final orderParentInt = Order.orderInt.contramap( (p) => p.value1, ); /// Order values of type [Parent] based on their `value2` ([double]). -final orderParentDouble = Order.orderDouble().contramap( +final orderParentDouble = Order.orderDouble.contramap( (p) => p.value2, ); ``` diff --git a/packages/fpdart/example/src/map/overview.dart b/packages/fpdart/example/src/map/overview.dart index 27738037..05e21e88 100644 --- a/packages/fpdart/example/src/map/overview.dart +++ b/packages/fpdart/example/src/map/overview.dart @@ -1,5 +1,4 @@ -import 'package:fpdart/src/date.dart'; -import 'package:fpdart/src/extension/map_extension.dart'; +import 'package:fpdart/fpdart.dart'; void main() { final d1 = DateTime(2001, 1, 1); @@ -9,8 +8,14 @@ void main() { /// /// The first date `d1` will be overwritten by the second date `d2`, /// since the year is the same. - final map = {}.upsertAt(dateEqYear, d1, 1).upsertAt( - dateEqYear, + final map = {} + .upsertAt( + Eq.dateEqYear, + d1, + 1, + ) + .upsertAt( + Eq.dateEqYear, d2, 2, ); diff --git a/packages/fpdart/example/src/typeclass/eq/eq1.dart b/packages/fpdart/example/src/typeclass/eq/eq1.dart index 3187973c..d2488f2a 100644 --- a/packages/fpdart/example/src/typeclass/eq/eq1.dart +++ b/packages/fpdart/example/src/typeclass/eq/eq1.dart @@ -8,12 +8,12 @@ class Parent { void main() { /// Equality for values of type [Parent] based on their `value1` ([int]). - final eqParentInt = Eq.eqInt().contramap( + final eqParentInt = Eq.eqInt.contramap( (p) => p.value1, ); /// Equality for of type [Parent] based on their `value2` ([double]). - final eqParentDouble = Eq.eqDouble().contramap( + final eqParentDouble = Eq.eqDouble.contramap( (p) => p.value2, ); } diff --git a/packages/fpdart/example/src/typeclass/order/order2.dart b/packages/fpdart/example/src/typeclass/order/order2.dart index 7b7b269e..d05a9c4d 100644 --- a/packages/fpdart/example/src/typeclass/order/order2.dart +++ b/packages/fpdart/example/src/typeclass/order/order2.dart @@ -8,12 +8,12 @@ class Parent { void main() { /// Order values of type [Parent] based on their `value1` ([int]). - final orderParentInt = Order.orderInt().contramap( + final orderParentInt = Order.orderInt.contramap( (p) => p.value1, ); /// Order values of type [Parent] based on their `value2` ([double]). - final orderParentDouble = Order.orderDouble().contramap( + final orderParentDouble = Order.orderDouble.contramap( (p) => p.value2, ); } diff --git a/packages/fpdart/lib/src/date.dart b/packages/fpdart/lib/src/date.dart index 068cc7fa..5d6ce63c 100644 --- a/packages/fpdart/lib/src/date.dart +++ b/packages/fpdart/lib/src/date.dart @@ -1,6 +1,4 @@ import 'io.dart'; -import 'typeclass/eq.dart'; -import 'typeclass/order.dart'; /// Constructs a [DateTime] instance with current date and time in the local time zone. /// @@ -13,31 +11,3 @@ IO get dateNow => IO(() => DateTime.now()); /// /// [IO] wrapper around dart `DateTime.now().millisecondsSinceEpoch`. IO get now => dateNow.map((date) => date.millisecondsSinceEpoch); - -/// [Order] instance on dart [DateTime]. -final Order dateOrder = Order.from( - (a1, a2) => a1.compareTo(a2), -); - -/// [Eq] instance to compare [DateTime] years. -final Eq dateEqYear = Eq.instance( - (a1, a2) => a1.year == a2.year, -); - -/// [Eq] instance to compare [DateTime] months. -final Eq dateEqMonth = Eq.instance( - (a1, a2) => a1.month == a2.month, -); - -/// [Eq] instance to compare [DateTime] days. -final Eq dateEqDay = Eq.instance( - (a1, a2) => a1.day == a2.day, -); - -/// [Eq] instance to compare [DateTime] by year, month, and day. -final Eq dateEqYearMonthDay = Eq.instance( - (a1, a2) => - dateEqYear.eqv(a1, a2) && - dateEqMonth.eqv(a1, a2) && - dateEqDay.eqv(a1, a2), -); diff --git a/packages/fpdart/lib/src/extension/date_time_extension.dart b/packages/fpdart/lib/src/extension/date_time_extension.dart index ec827975..fa431676 100644 --- a/packages/fpdart/lib/src/extension/date_time_extension.dart +++ b/packages/fpdart/lib/src/extension/date_time_extension.dart @@ -1,16 +1,17 @@ -import '../date.dart'; +import '../typeclass/eq.dart'; /// `fpdart` extension methods on [DateTime] extension FpdartOnDateTime on DateTime { /// Return `true` when this [DateTime] and `other` have the same **year**. - bool eqvYear(DateTime other) => dateEqYear.eqv(this, other); + bool eqvYear(DateTime other) => Eq.dateEqYear.eqv(this, other); /// Return `true` when this [DateTime] and `other` have the same **month**. - bool eqvMonth(DateTime other) => dateEqMonth.eqv(this, other); + bool eqvMonth(DateTime other) => Eq.dateEqMonth.eqv(this, other); /// Return `true` when this [DateTime] and `other` have the same **day**. - bool eqvDay(DateTime other) => dateEqDay.eqv(this, other); + bool eqvDay(DateTime other) => Eq.dateEqDay.eqv(this, other); /// Return `true` when this [DateTime] and `other` have the same **year, month, and day**. - bool eqvYearMonthDay(DateTime other) => dateEqYearMonthDay.eqv(this, other); + bool eqvYearMonthDay(DateTime other) => + Eq.dateEqYearMonthDay.eqv(this, other); } diff --git a/packages/fpdart/lib/src/extension/iterable_extension.dart b/packages/fpdart/lib/src/extension/iterable_extension.dart index 09048d91..b9a99203 100644 --- a/packages/fpdart/lib/src/extension/iterable_extension.dart +++ b/packages/fpdart/lib/src/extension/iterable_extension.dart @@ -1,4 +1,3 @@ -import '../date.dart'; import '../function.dart'; import '../option.dart'; import '../typeclass/eq.dart'; @@ -346,7 +345,7 @@ extension FpdartOnIterable on Iterable { /// /// Sorting [DateTime] in **ascending** order (older dates first). List sortWithDate(DateTime Function(T instance) getDate) => - sortWith(getDate, dateOrder); + sortWith(getDate, Order.orderDate); } /// Functional programming functions on `Iterable>` using `fpdart`. diff --git a/packages/fpdart/lib/src/typeclass/eq.dart b/packages/fpdart/lib/src/typeclass/eq.dart index 45201a5c..6e828990 100644 --- a/packages/fpdart/lib/src/typeclass/eq.dart +++ b/packages/fpdart/lib/src/typeclass/eq.dart @@ -40,12 +40,12 @@ abstract class Eq { /// } /// /// Equality for values of type [Parent] based on their `value1` ([int]). - /// final eqParentInt = Eq.eqInt().contramap( + /// final eqParentInt = Eq.eqInt.contramap( /// (p) => p.value1, /// ); /// /// Equality for of type [Parent] based on their `value2` ([double]). - /// final eqParentDouble = Eq.eqDouble().contramap( + /// final eqParentDouble = Eq.eqDouble.contramap( /// (p) => p.value2, /// ); /// ``` @@ -84,19 +84,42 @@ abstract class Eq { static Eq allEqual() => _Eq((x, y) => true); /// Instance of `Eq` for `num` using the standard `==` operator. - static Eq eqNum() => _Eq((x, y) => x == y); + static Eq eqNum = _Eq((x, y) => x == y); /// Instance of `Eq` for `int` using the standard `==` operator. - static Eq eqInt() => _Eq((x, y) => x == y); + static Eq eqInt = _Eq((x, y) => x == y); /// Instance of `Eq` for `double` using the standard `==` operator. - static Eq eqDouble() => _Eq((x, y) => x == y); + static Eq eqDouble = _Eq((x, y) => x == y); /// Instance of `Eq` for `String` using the standard `==` operator. - static Eq eqString() => _Eq((x, y) => x == y); + static Eq eqString = _Eq((x, y) => x == y); /// Instance of `Eq` for `bool` using the standard `==` operator. - static Eq eqBool() => _Eq((x, y) => x == y); + static Eq eqBool = _Eq((x, y) => x == y); + + /// [Eq] instance to compare [DateTime] years. + static Eq dateEqYear = _Eq( + (a1, a2) => a1.year == a2.year, + ); + + /// [Eq] instance to compare [DateTime] months. + static Eq dateEqMonth = _Eq( + (a1, a2) => a1.month == a2.month, + ); + + /// [Eq] instance to compare [DateTime] days. + static Eq dateEqDay = _Eq( + (a1, a2) => a1.day == a2.day, + ); + + /// [Eq] instance to compare [DateTime] by year, month, and day. + static Eq dateEqYearMonthDay = _Eq( + (a1, a2) => + dateEqYear.eqv(a1, a2) && + dateEqMonth.eqv(a1, a2) && + dateEqDay.eqv(a1, a2), + ); } class _Eq extends Eq { diff --git a/packages/fpdart/lib/src/typeclass/order.dart b/packages/fpdart/lib/src/typeclass/order.dart index 11826d61..a3f07766 100644 --- a/packages/fpdart/lib/src/typeclass/order.dart +++ b/packages/fpdart/lib/src/typeclass/order.dart @@ -51,12 +51,12 @@ abstract class Order extends PartialOrder { /// } /// /// /// Order values of type [Parent] based on their `value1` ([int]). - /// final orderParentInt = Order.orderInt().contramap( + /// final orderParentInt = Order.orderInt.contramap( /// (p) => p.value1, /// ); /// /// /// Order values of type [Parent] based on their `value2` ([double]). - /// final orderParentDouble = Order.orderDouble().contramap( + /// final orderParentDouble = Order.orderDouble.contramap( /// (p) => p.value2, /// ); /// ``` @@ -114,26 +114,31 @@ abstract class Order extends PartialOrder { /// (`compare` always returns `0`). static Order allEqual() => _Order((x, y) => 0); - /// Instance of `Eq` for `int`. - static Order orderInt() => _Order((x, y) => x == y + /// Instance of `Order` for `int`. + static Order orderInt = _Order((x, y) => x == y ? 0 : x > y ? 1 : -1); - /// Instance of `Eq` for `num`. - static Order orderNum() => _Order((x, y) => x == y + /// Instance of `Order` for `num`. + static Order orderNum = _Order((x, y) => x == y ? 0 : x > y ? 1 : -1); - /// Instance of `Eq` for `double`. - static Order orderDouble() => _Order((x, y) => x == y + /// Instance of `Order` for `double`. + static Order orderDouble = _Order((x, y) => x == y ? 0 : x > y ? 1 : -1); + + /// Instance of `Order` for [DateTime]. + static Order orderDate = _Order( + (a1, a2) => a1.compareTo(a2), + ); } class _Order extends Order { diff --git a/packages/fpdart/test/src/date_test.dart b/packages/fpdart/test/src/date_test.dart index c45da427..5ec96846 100644 --- a/packages/fpdart/test/src/date_test.dart +++ b/packages/fpdart/test/src/date_test.dart @@ -5,7 +5,7 @@ void main() { group('date', () { group('[Property-based testing]', () { Glados2().test('dateOrder', (d1, d2) { - final compare = dateOrder.compare(d1, d2); + final compare = Order.orderDate.compare(d1, d2); expect( compare, d1.isAfter(d2) @@ -16,17 +16,17 @@ void main() { }); Glados2().test('dateEqYear', (d1, d2) { - final compare = dateEqYear.eqv(d1, d2); + final compare = Eq.dateEqYear.eqv(d1, d2); expect(compare, d1.year == d2.year); }); Glados2().test('dateEqMonth', (d1, d2) { - final compare = dateEqMonth.eqv(d1, d2); + final compare = Eq.dateEqMonth.eqv(d1, d2); expect(compare, d1.month == d2.month); }); Glados2().test('dateEqYearMonthDay', (d1, d2) { - final compare = dateEqYearMonthDay.eqv(d1, d2); + final compare = Eq.dateEqYearMonthDay.eqv(d1, d2); expect(compare, d1.year == d2.year && d1.month == d2.month && d1.day == d2.day); }); @@ -53,25 +53,14 @@ void main() { }); }); - test('dateOrder', () { - final prevDate = DateTime(2020); - final currDate = DateTime(2021); - final compareNegative = dateOrder.compare(prevDate, currDate); - final comparePositive = dateOrder.compare(currDate, prevDate); - final compareSame = dateOrder.compare(currDate, currDate); - expect(compareNegative, -1); - expect(comparePositive, 1); - expect(compareSame, 0); - }); - test('dateEqYear', () { final date1 = DateTime(2021, 2, 2); final date2 = DateTime(2021, 3, 3); final date3 = DateTime(2020, 2, 2); - expect(dateEqYear.eqv(date1, date1), true); - expect(dateEqYear.eqv(date1, date2), true); - expect(dateEqYear.eqv(date1, date3), false); + expect(Eq.dateEqYear.eqv(date1, date1), true); + expect(Eq.dateEqYear.eqv(date1, date2), true); + expect(Eq.dateEqYear.eqv(date1, date3), false); }); test('dateEqMonth', () { @@ -79,9 +68,9 @@ void main() { final date2 = DateTime(2021, 3, 3); final date3 = DateTime(2020, 2, 2); - expect(dateEqMonth.eqv(date1, date1), true); - expect(dateEqMonth.eqv(date1, date2), false); - expect(dateEqMonth.eqv(date1, date3), true); + expect(Eq.dateEqMonth.eqv(date1, date1), true); + expect(Eq.dateEqMonth.eqv(date1, date2), false); + expect(Eq.dateEqMonth.eqv(date1, date3), true); }); test('dateEqDay', () { @@ -89,9 +78,9 @@ void main() { final date2 = DateTime(2021, 3, 3); final date3 = DateTime(2020, 3, 2); - expect(dateEqDay.eqv(date1, date1), true); - expect(dateEqDay.eqv(date1, date2), false); - expect(dateEqDay.eqv(date1, date3), true); + expect(Eq.dateEqDay.eqv(date1, date1), true); + expect(Eq.dateEqDay.eqv(date1, date2), false); + expect(Eq.dateEqDay.eqv(date1, date3), true); }); test('dateEqYearMonthDay', () { @@ -99,9 +88,9 @@ void main() { final date2 = DateTime(2021, 2, 2, 11, 11); final date3 = DateTime(2020, 2, 2, 12, 12); - expect(dateEqYearMonthDay.eqv(date1, date1), true); - expect(dateEqYearMonthDay.eqv(date1, date2), true); - expect(dateEqYearMonthDay.eqv(date1, date3), false); + expect(Eq.dateEqYearMonthDay.eqv(date1, date1), true); + expect(Eq.dateEqYearMonthDay.eqv(date1, date2), true); + expect(Eq.dateEqYearMonthDay.eqv(date1, date3), false); }); }); } diff --git a/packages/fpdart/test/src/eq_test.dart b/packages/fpdart/test/src/eq_test.dart index 573ac8ba..4ed1b948 100644 --- a/packages/fpdart/test/src/eq_test.dart +++ b/packages/fpdart/test/src/eq_test.dart @@ -85,7 +85,7 @@ void main() { }); test('.eqNum', () { - final eq = Eq.eqNum(); + final eq = Eq.eqNum; expect(eq.eqv(10, 10), true); expect(eq.eqv(10.0, 10), true); expect(eq.eqv(10.5, 10.5), true); @@ -94,7 +94,7 @@ void main() { }); test('.eqInt', () { - final eq = Eq.eqInt(); + final eq = Eq.eqInt; expect(eq.eqv(10, 10), true); expect(eq.eqv(11, 10), false); expect(eq.eqv(-10, -10), true); @@ -102,7 +102,7 @@ void main() { }); test('.eqDouble', () { - final eq = Eq.eqDouble(); + final eq = Eq.eqDouble; expect(eq.eqv(10, 10), true); expect(eq.eqv(10.0, 10), true); expect(eq.eqv(10.5, 10.5), true); @@ -111,7 +111,7 @@ void main() { }); test('.eqString', () { - final eq = Eq.eqString(); + final eq = Eq.eqString; expect(eq.eqv("abc", "abc"), true); expect(eq.eqv("abc", "abd"), false); expect(eq.eqv("abc", "ab"), false); @@ -120,7 +120,7 @@ void main() { }); test('.eqBool', () { - final eq = Eq.eqBool(); + final eq = Eq.eqBool; expect(eq.eqv(true, true), true); expect(eq.eqv(false, true), false); expect(eq.eqv(true, false), false); @@ -129,7 +129,7 @@ void main() { group('contramap', () { test('int', () { - final eqParentInt = Eq.eqInt().contramap<_Parent>( + final eqParentInt = Eq.eqInt.contramap<_Parent>( (p) => p.value1, ); @@ -157,7 +157,7 @@ void main() { }); test('double', () { - final eqParentDouble = Eq.eqDouble().contramap<_Parent>( + final eqParentDouble = Eq.eqDouble.contramap<_Parent>( (p) => p.value2, ); diff --git a/packages/fpdart/test/src/extension/date_time_extension_test.dart b/packages/fpdart/test/src/extension/date_time_extension_test.dart index 049272ca..dc539516 100644 --- a/packages/fpdart/test/src/extension/date_time_extension_test.dart +++ b/packages/fpdart/test/src/extension/date_time_extension_test.dart @@ -6,20 +6,20 @@ void main() { group('FpdartOnDateTime', () { group('[Property-based testing]', () { Glados2().test('eqvYear == dateEqYear', (d1, d2) { - expect(d1.eqvYear(d2), dateEqYear.eqv(d1, d2)); + expect(d1.eqvYear(d2), Eq.dateEqYear.eqv(d1, d2)); }); Glados2().test('eqvMonth == dateEqMonth', (d1, d2) { - expect(d1.eqvMonth(d2), dateEqMonth.eqv(d1, d2)); + expect(d1.eqvMonth(d2), Eq.dateEqMonth.eqv(d1, d2)); }); Glados2().test('eqvDay == dateEqDay', (d1, d2) { - expect(d1.eqvDay(d2), dateEqDay.eqv(d1, d2)); + expect(d1.eqvDay(d2), Eq.dateEqDay.eqv(d1, d2)); }); Glados2().test('eqvYearMonthDay == dateEqYear', (d1, d2) { - expect(d1.eqvYearMonthDay(d2), dateEqYear.eqv(d1, d2)); + expect(d1.eqvYearMonthDay(d2), Eq.dateEqYear.eqv(d1, d2)); }); }); diff --git a/packages/fpdart/test/src/extension/iterable_extension_test.dart b/packages/fpdart/test/src/extension/iterable_extension_test.dart index 5b94e806..c4748340 100644 --- a/packages/fpdart/test/src/extension/iterable_extension_test.dart +++ b/packages/fpdart/test/src/extension/iterable_extension_test.dart @@ -94,7 +94,7 @@ void main() { ]; final ap = list1.insertWith( (instance) => instance.date, - dateOrder, + Order.orderDate, SortDate(5, DateTime(2021)), ); @@ -116,7 +116,7 @@ void main() { SortDate(1, DateTime(2020)), SortDate(3, DateTime(2018)), ]; - final ap = list1.sortWith((instance) => instance.date, dateOrder); + final ap = list1.sortWith((instance) => instance.date, Order.orderDate); expect(ap.elementAt(0).id, 4); expect(ap.elementAt(1).id, 3); diff --git a/packages/fpdart/test/src/extension/map_extension_test.dart b/packages/fpdart/test/src/extension/map_extension_test.dart index 306114ba..8883e4f0 100644 --- a/packages/fpdart/test/src/extension/map_extension_test.dart +++ b/packages/fpdart/test/src/extension/map_extension_test.dart @@ -80,7 +80,9 @@ void main() { DateTime(2000, 1, 1): 1, DateTime(2001, 1, 1): 2, }, (value) { - value.lookupEq(dateEqYear, DateTime(2000, 10, 10)).matchTestSome((t) { + value + .lookupEq(Eq.dateEqYear, DateTime(2000, 10, 10)) + .matchTestSome((t) { expect(t, 1); }); }); @@ -91,7 +93,8 @@ void main() { DateTime(2000, 1, 1): 1, DateTime(2001, 1, 1): 2, }, (value) { - expect(value.lookupEq(dateEqYear, DateTime(2002, 1, 1)), isA()); + expect( + value.lookupEq(Eq.dateEqYear, DateTime(2002, 1, 1)), isA()); }); }); }); @@ -104,7 +107,7 @@ void main() { }, (value) { value .lookupWithKeyEq( - dateEqYear, + Eq.dateEqYear, DateTime(2000, 10, 10), ) .matchTestSome((t) { @@ -119,7 +122,7 @@ void main() { DateTime(2001, 1, 1): 2, }, (value) { expect( - value.lookupWithKeyEq(dateEqYear, DateTime(2002, 1, 1)), + value.lookupWithKeyEq(Eq.dateEqYear, DateTime(2002, 1, 1)), isA(), ); }); @@ -268,7 +271,7 @@ void main() { group('updateAt', () { test('Some', () { testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { - value.updateAt(Eq.eqString(), 'b', 10).matchTestSome( + value.updateAt(Eq.eqString, 'b', 10).matchTestSome( (t) => t.lookup('b').matchTestSome((t) { expect(t, 10); }), @@ -278,7 +281,7 @@ void main() { test('None', () { testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { - expect(value.updateAt(Eq.eqString(), 'e', 10), isA()); + expect(value.updateAt(Eq.eqString, 'e', 10), isA()); }); }); }); @@ -334,15 +337,15 @@ void main() { test('update', () { testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value) { - value.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { + value.lookupEq(Eq.eqString, 'b').matchTestSome((t) { expect(t, 2); }); - final result = value.upsertAt(Eq.eqString(), 'b', 10); - value.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { + final result = value.upsertAt(Eq.eqString, 'b', 10); + value.lookupEq(Eq.eqString, 'b').matchTestSome((t) { expect(t, 2); }); - result.lookupEq(Eq.eqString(), 'b').matchTestSome((t) { + result.lookupEq(Eq.eqString, 'b').matchTestSome((t) { expect(t, 10); }); }); @@ -355,20 +358,20 @@ void main() { final result = value .upsertAt( - dateEqYear, + Eq.dateEqYear, d1, 1, ) .upsertAt( - dateEqYear, + Eq.dateEqYear, d2, 2, ); - result.lookupEq(dateEqYear, d1).matchTestSome((t) { + result.lookupEq(Eq.dateEqYear, d1).matchTestSome((t) { expect(t, 2); }); - result.lookupEq(dateEqYear, d2).matchTestSome((t) { + result.lookupEq(Eq.dateEqYear, d2).matchTestSome((t) { expect(t, 2); }); }); @@ -538,8 +541,8 @@ void main() { testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { testImmutableMap({'a': 1, 'c': 3}, (value2) { final result = value2.isSubmap( - Eq.eqString(), - Eq.eqInt(), + Eq.eqString, + Eq.eqInt, value1, ); @@ -552,8 +555,8 @@ void main() { testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { testImmutableMap({'a': 1, 'c': 2}, (value2) { final result = value2.isSubmap( - Eq.eqString(), - Eq.eqInt(), + Eq.eqString, + Eq.eqInt, value1, ); @@ -566,8 +569,8 @@ void main() { testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { testImmutableMap({'a': 1, 'd': 3}, (value2) { final result = value2.isSubmap( - Eq.eqString(), - Eq.eqInt(), + Eq.eqString, + Eq.eqInt, value1, ); @@ -596,7 +599,7 @@ void main() { test('difference', () { testImmutableMap({'a': 1, 'b': 2, 'c': 3, 'd': 4}, (value1) { testImmutableMap({'a': 1, 'c': 3}, (value2) { - final result = value1.difference(Eq.eqString(), value2); + final result = value1.difference(Eq.eqString, value2); expect(result['a'], null); expect(result['b'], 2); expect(result['c'], null); diff --git a/packages/fpdart/test/src/order_test.dart b/packages/fpdart/test/src/order_test.dart index b1eee3be..500568b7 100644 --- a/packages/fpdart/test/src/order_test.dart +++ b/packages/fpdart/test/src/order_test.dart @@ -29,7 +29,7 @@ void main() { }); test('reverse', () { - final instance = Order.orderInt(); + final instance = Order.orderInt; final reverse = instance.reverse; expect(reverse.compare(1, 1), 0); expect(reverse.compare(1, 2), 1); @@ -124,7 +124,7 @@ void main() { }); test('between', () { - final instance = Order.orderInt(); + final instance = Order.orderInt; expect(instance.between(0, 10, 4), true); expect(instance.between(0, 0, 0), true); expect(instance.between(-1, 0, 0), true); @@ -133,7 +133,7 @@ void main() { }); test('clamp', () { - final instance = Order.orderInt(); + final instance = Order.orderInt; expect(instance.clamp(1, 10, 2), 2); expect(instance.clamp(1, 10, 10), 10); expect(instance.clamp(1, 10, 20), 10); @@ -141,8 +141,19 @@ void main() { expect(instance.clamp(1, 10, -10), 1); }); + test('orderDate', () { + final prevDate = DateTime(2020); + final currDate = DateTime(2021); + final compareNegative = Order.orderDate.compare(prevDate, currDate); + final comparePositive = Order.orderDate.compare(currDate, prevDate); + final compareSame = Order.orderDate.compare(currDate, currDate); + expect(compareNegative, -1); + expect(comparePositive, 1); + expect(compareSame, 0); + }); + test('orderNum', () { - final ord = Order.orderNum(); + final ord = Order.orderNum; expect(ord.eqv(10, 10), true); expect(ord.eqv(10.0, 10), true); expect(ord.gt(10, 0), true); @@ -151,7 +162,7 @@ void main() { }); test('orderDouble', () { - final ord = Order.orderDouble(); + final ord = Order.orderDouble; expect(ord.eqv(10.5, 10.5), true); expect(ord.eqv(10.0, 10), true); expect(ord.gt(1.5, 1.2), true); @@ -160,7 +171,7 @@ void main() { }); test('orderInt', () { - final ord = Order.orderInt(); + final ord = Order.orderInt; expect(ord.eqv(10, 10), true); expect(ord.eqv(-10, 10), false); expect(ord.gt(1, 1), false); @@ -170,7 +181,7 @@ void main() { group('contramap', () { test('int', () { - final orderParentInt = Order.orderInt().contramap<_Parent>( + final orderParentInt = Order.orderInt.contramap<_Parent>( (p) => p.value1, ); @@ -198,7 +209,7 @@ void main() { }); test('double', () { - final orderParentDouble = Order.orderDouble().contramap<_Parent>( + final orderParentDouble = Order.orderDouble.contramap<_Parent>( (p) => p.value2, ); From 530c03a9788a66beb706bb334ca9fbc9b6842de9 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 18:03:54 +0200 Subject: [PATCH 60/71] updated docs --- packages/fpdart/CHANGELOG.md | 38 ++++++++++++++++--- packages/fpdart/README.md | 12 ++++++ .../fpdart/example/src/either/overview.dart | 6 +++ .../fpdart/example/src/option/overview.dart | 6 +++ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 05bb9280..0579079c 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -1,4 +1,4 @@ -## v1.0.0 - Soon +## v1.0.0-beta.1 - 27 May 2023 - Minimum environment dart sdk to `3.0.0` ⚠️ (Dart 3️⃣) ```yaml environment: @@ -9,8 +9,34 @@ environment: - Added new `ReaderTask` type - `Either` as `sealed` class (Dart 3️⃣) - You can now use exhaustive pattern matching (`Left` or `Right`) +```dart +/// Pattern matching +final match = right.match( + (l) => print('Left($l)'), + (r) => print('Right($r)'), +); + +/// or use Dart's pattern matching as well 🤝 +final dartMatch = switch (right) { + Left(value: final l) => 'Left($l)', + Right(value: final r) => 'Right($r)', +}; +``` - `Option` as `sealed` class (Dart 3️⃣) - You can now use exhaustive pattern matching (`None` or `Some`) +```dart +/// Pattern matching +final match = option.match( + () => print('None'), + (a) => print('Some($a)'), +); + +/// or use Dart's pattern matching as well 🤝 +final dartMatch = switch (option) { + None() => 'None', + Some(value: final a) => 'Some($a)', +}; +``` - Types marked as `final` (no `extends` nor `implements`) (Dart 3️⃣) - `Unit` - `Reader` @@ -36,8 +62,8 @@ environment: - `difference` (`Iterable`) - `filterWithIndex` (`Iterable`) - Fixed the following methods ⚠️ - - `takeWhileRight`: Result `List` reversed - - `dropWhileRight`: Result `List` reversed + - `takeWhileRight`: Resulting `List` now in reversed order as expected + - `dropWhileRight`: Resulting `List` now in reversed order as expected - Updated the following methods ⚠️ - `foldRight`, `foldRightWithIndex` (`List`): Changed parameter order in `combine` function - `zipWith` (`Iterable`): Changed parameters definition, no more curried @@ -160,7 +186,7 @@ final add = addFunction.curry; /// Before final eq = dateEqYear; // Global -/// New +/// Now final eq = Eq.dateEqYear; ``` - Added `Eq` instances for `num`, `int`, `double`, `String`, and `bool` @@ -191,7 +217,7 @@ final eqParentDouble = Eq.eqDouble.contramap( /// Before final reversed = Order.reverse(instance); -/// Before +/// Now final reversed = instance.reverse; ``` - Moved `DateTime` instances of `Order` as `Order` static members @@ -225,7 +251,7 @@ final boolValue = Random().nextBool(); final result = boolValue.match(() => -1, () => 1); final result = boolValue.fold(() => -1, () => 1); -/// New +/// Now final result = boolValue ? 1 : -1; final result = switch (boolValue) { true => 1, false => -1 }; ``` diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index 17a4d93b..e4fb1473 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -152,6 +152,12 @@ final match = option.match( (a) => print('Some($a)'), ); +/// or use Dart's pattern matching as well 🤝 +final dartMatch = switch (option) { + None() => 'None', + Some(value: final a) => 'Some($a)', +}; + /// Convert to [Either] final either = option.toEither(() => 'missing'); @@ -199,6 +205,12 @@ final match = right.match( (r) => print('Right($r)'), ); +/// or use Dart's pattern matching as well 🤝 +final dartMatch = switch (right) { + Left(value: final l) => 'Left($l)', + Right(value: final r) => 'Right($r)', +}; + /// Convert to [Option] final option = right.toOption(); ``` diff --git a/packages/fpdart/example/src/either/overview.dart b/packages/fpdart/example/src/either/overview.dart index e52b3fb7..f22dd7ad 100644 --- a/packages/fpdart/example/src/either/overview.dart +++ b/packages/fpdart/example/src/either/overview.dart @@ -49,6 +49,12 @@ void main() { (r) => print('Right($r)'), ); + /// or use Dart's pattern matching as well 🤝 + final dartMatch = switch (right) { + Left(value: final l) => 'Left($l)', + Right(value: final r) => 'Right($r)', + }; + /// Convert to [Option] final option = right.toOption(); } diff --git a/packages/fpdart/example/src/option/overview.dart b/packages/fpdart/example/src/option/overview.dart index 435711b7..c1e103ff 100644 --- a/packages/fpdart/example/src/option/overview.dart +++ b/packages/fpdart/example/src/option/overview.dart @@ -49,6 +49,12 @@ void main() { (a) => print('Some($a)'), ); + /// or use Dart's pattern matching as well 🤝 + final dartMatch = switch (option) { + None() => 'None', + Some(value: final a) => 'Some($a)', + }; + /// Convert to [Either] final either = option.toEither(() => 'missing'); From eab4e06e1ac8f720e28c6f6422b5adf97733f02f Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 18:26:04 +0200 Subject: [PATCH 61/71] new immutable collection README --- packages/fpdart/README.md | 37 ++++++++++++++++++- .../fpdart/example/src/list/overview.dart | 15 ++++++++ .../lib/src/extension/map_extension.dart | 2 - 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 packages/fpdart/example/src/list/overview.dart diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index e4fb1473..322ca556 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -331,9 +331,42 @@ You have access to a `$` function, that you can use to extract and use the value ### 📦 Immutable Collections -Fpdart provides some extension methods on `Iterable` (`List`) and `Map` that extend the methods available by providing some functional programming signatures (safe methods that never mutate the original collection and that never throw exceptions). +> If you are going to use `fpdart` in your project, make sure to use immutable collections as well ☝️ + +Immutability is **at the core of functional programming** (alongside pure functions 🤝). + +`fpdart` does **not** provide immutable collections. Instead, we strongly suggest to use the [`fast_immutable_collections`](https://pub.dev/packages/fast_immutable_collections) package. + +[`fast_immutable_collections`](https://pub.dev/packages/fast_immutable_collections) provides all the necessary immutable collections (`List`, `Set`, `Map` and more) with an extensive API and [top-class performance](https://pub.dev/packages/fast_immutable_collections#12-benchmarks). + + +`fpdart` instead provides some extension methods on `Iterable`, `List`, and `Map`. These are designed to **extend the native Dart API with immutable methods**, as well as providing many handy additional functions: + +```dart +/// Dart: `1` +[1, 2, 3, 4].first; + +/// fpdart: `Some(1)` +[1, 2, 3, 4].head; + +/// Dart: Throws a [StateError] ⚠️ +[].first; + +/// fpdart: `None()` +[].head; + +final map = {'a': 1, 'b': 2}; + +/// Dart: mutable ⚠️ +map.updateAll((key, value) => value + 10); + +/// fpdart: immutable equivalent 🤝 +final newMap = map.mapValue((value) => value + 10); +``` + + +You can use these extension methods on any native Dart collection and **also** in combination with [`fast_immutable_collections`](https://pub.dev/packages/fast_immutable_collections) immutable collections. -Integrations for immutable collections (`IList`, `ISet`, `IMap`, etc.) are still being discussed with the community. `fpdart` does not want to be another immutable collection solution in the ecosystem. That is why we are working to integrate `fpdart` with other more mature packages that already implements immutable collections. Stay tuned! ### More diff --git a/packages/fpdart/example/src/list/overview.dart b/packages/fpdart/example/src/list/overview.dart new file mode 100644 index 00000000..3d811213 --- /dev/null +++ b/packages/fpdart/example/src/list/overview.dart @@ -0,0 +1,15 @@ +import 'package:fpdart/fpdart.dart'; + +void main() { + /// Dart: `1` + [1, 2, 3, 4].first; + + /// fpdart: `Some(1)` + [1, 2, 3, 4].head; + + /// Dart: Throws a [StateError] ⚠️ + [].first; + + /// fpdart: `None()` + [].head; +} diff --git a/packages/fpdart/lib/src/extension/map_extension.dart b/packages/fpdart/lib/src/extension/map_extension.dart index ba5627a4..7e32d40b 100644 --- a/packages/fpdart/lib/src/extension/map_extension.dart +++ b/packages/fpdart/lib/src/extension/map_extension.dart @@ -11,8 +11,6 @@ extension FpdartOnMap on Map { /// Convert each **value** of the [Map] using /// the `update` function and returns a new [Map]. - /// - /// Immutable version of `Map.updateAll`. Map mapValue(A Function(V value) update) => map( (key, value) => MapEntry(key, update(value)), ); From db9f351d6b7b2f33539f6875792f292294f6fd6d Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 26 May 2023 18:27:50 +0200 Subject: [PATCH 62/71] v1.0.0-beta.1 --- packages/fpdart/README.md | 6 +++++- packages/fpdart/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index 322ca556..c8ba6e37 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -103,7 +103,7 @@ Check out also this series of articles about functional programming with `fpdart ```yaml # pubspec.yaml dependencies: - fpdart: ^0.6.0 # Check out the latest version + fpdart: ^1.0.0-beta.1 # Check out the latest version ``` ## ✨ Examples @@ -462,6 +462,10 @@ In general, **any contribution or feedback is welcome** (and encouraged!). ## 📃 Versioning +- **v1.0.0-beta.1** - 27 May 2023 + +*** + - **v0.6.0** - 6 May 2023 - **v0.5.0** - 4 March 2023 - v0.4.1 - 25 February 2023 diff --git a/packages/fpdart/pubspec.yaml b/packages/fpdart/pubspec.yaml index e91a706e..017f801a 100644 --- a/packages/fpdart/pubspec.yaml +++ b/packages/fpdart/pubspec.yaml @@ -2,7 +2,7 @@ name: fpdart description: > Functional programming in Dart and Flutter. All the main functional programming types and patterns fully documented, tested, and with examples. -version: 1.0.0 +version: 1.0.0-beta.1 homepage: https://www.sandromaglione.com/ repository: https://github.com/SandroMaglione/fpdart author: Maglione Sandro From 01ba9a1ff252de6c6594f4734f417441003c0e8b Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 27 May 2023 10:29:32 +0200 Subject: [PATCH 63/71] fix generic for applicative fixes slight typo in lib/src/class/applicative.dart #120 --- packages/fpdart/lib/src/typeclass/applicative.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fpdart/lib/src/typeclass/applicative.dart b/packages/fpdart/lib/src/typeclass/applicative.dart index 9856a49c..dac17939 100644 --- a/packages/fpdart/lib/src/typeclass/applicative.dart +++ b/packages/fpdart/lib/src/typeclass/applicative.dart @@ -18,7 +18,7 @@ mixin Applicative2 on HKT2, Functor2 { } mixin Applicative3 on HKT3, Functor3 { - HKT3 pure(C a); + HKT3 pure(D a); HKT3 ap(HKT3 a); @override From cc8916ce26a5df7e875e6823e76011ceff67f753 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 27 May 2023 10:46:23 +0200 Subject: [PATCH 64/71] curryLast --- packages/fpdart/CHANGELOG.md | 5 ++-- .../lib/src/extension/curry_extension.dart | 25 +++++++++++++++++++ packages/fpdart/lib/src/function.dart | 4 +-- .../src/extension/curry_extension_test.dart | 22 +++++++++++++++- packages/fpdart/test/src/function_test.dart | 2 +- 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 0579079c..f3e88654 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -106,7 +106,7 @@ final dartMatch = switch (option) { - `isSubmap` no more curried - `collect` no more curried - `difference` no more curried -- Added conversions helpers from `String` to `num`, `int`, `double`, and `bool` using `Option` and `Either` (both as extension methods on `String` and as functions) +- Added conversions helpers from `String` to `num`, `int`, `double`, and `bool` using `Option` and `Either` (both as extension methods on `String` and as functions) ([#80](https://github.com/SandroMaglione/fpdart/issues/80)) - `toNumOption` - `toIntOption` - `toDoubleOption` @@ -128,7 +128,7 @@ final result = toNumOption("10"); /// `Some(10)` final result = toNumOption("10.5"); /// `Some(10.5)` final result = toIntOption("0xFF"); /// `Some(255)` final result = toDoubleOption("10.5"); /// `Some(10.5)` -final result = toBoolEither(() => "left")("NO"); /// `Left("left")` +final result = toBoolEither("NO", () => "left"); /// `Left("left")` ``` - Changed `dateNow`, `now`, `random`, and `randomBool` to getter functions ```dart @@ -157,6 +157,7 @@ final isStringWithEvenLength = isEven.contramap((n) => n.length); - Changed definition of `curry` to curry only the first parameter - Changed `uncurry` and `curry` extension to getter function - Removed `curry` and `uncurry` as functions (use extension method instead) + - Added `curryLast` (curry **last** parameter) ```dart int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; diff --git a/packages/fpdart/lib/src/extension/curry_extension.dart b/packages/fpdart/lib/src/extension/curry_extension.dart index 43042cb2..2b1ca4ee 100644 --- a/packages/fpdart/lib/src/extension/curry_extension.dart +++ b/packages/fpdart/lib/src/extension/curry_extension.dart @@ -1,6 +1,10 @@ /// {@template fpdart_curry_extension} /// Extract first parameter from this function to allow curring. /// {@endtemplate} +/// +/// {@template fpdart_curry_last_extension} +/// Extract **last** parameter from this function to allow curring. +/// {@endtemplate} extension CurryExtension2 on Output Function( Input1, Input2) { @@ -10,6 +14,13 @@ extension CurryExtension2 on Output Function( /// Inverse of `uncurry`. Output Function(Input2) Function(Input1) get curry => (input1) => (input2) => this(input1, input2); + + /// Convert this function from accepting two parameters to a function + /// that returns another function both accepting one parameter. + /// + /// Curry the **last** parameter in the function. + Output Function(Input1) Function(Input2) get curryLast => + (input2) => (input1) => this(input1, input2); } extension UncurryExtension2 on Output Function(Input2) @@ -34,6 +45,10 @@ extension CurryExtension3 on Output Function( /// {@macro fpdart_curry_extension} Output Function(Input2, Input3) Function(Input1) get curry => (input1) => (input2, input3) => this(input1, input2, input3); + + /// {@macro fpdart_curry_last_extension} + Output Function(Input1, Input2) Function(Input3) get curryLast => + (input3) => (input1, input2) => this(input1, input2, input3); } extension UncurryExtension3 @@ -60,6 +75,11 @@ extension CurryExtension4 on Output Output Function(Input2, Input3, Input4) Function(Input1) get curry => (input1) => (input2, input3, input4) => this(input1, input2, input3, input4); + + /// {@macro fpdart_curry_last_extension} + Output Function(Input1, Input2, Input3) Function(Input4) get curryLast => + (input4) => + (input1, input2, input3) => this(input1, input2, input3, input4); } extension UncurryExtension4 @@ -89,6 +109,11 @@ extension CurryExtension5 Output Function(Input2, Input3, Input4, Input5) Function(Input1) get curry => (input1) => (input2, input3, input4, input5) => this(input1, input2, input3, input4, input5); + + /// {@macro fpdart_curry_last_extension} + Output Function(Input1, Input2, Input3, Input4) Function(Input5) + get curryLast => (input5) => (input1, input2, input3, input4) => + this(input1, input2, input3, input4, input5); } extension UncurryExtension5 diff --git a/packages/fpdart/lib/src/function.dart b/packages/fpdart/lib/src/function.dart index b4473be2..e7963fdd 100644 --- a/packages/fpdart/lib/src/function.dart +++ b/packages/fpdart/lib/src/function.dart @@ -84,5 +84,5 @@ Either Function(String) toDoubleEither(L Function() onLeft) => /// {@template fpdart_string_extension_to_bool_either} /// Convert this [String] to [bool], returns the result of `onLeft` for invalid inputs. /// {@endtemplate} -Either Function(String) toBoolEither(L Function() onLeft) => - (str) => str.toBoolEither(onLeft); +Either toBoolEither(String str, L Function() onLeft) => + str.toBoolEither(onLeft); diff --git a/packages/fpdart/test/src/extension/curry_extension_test.dart b/packages/fpdart/test/src/extension/curry_extension_test.dart index a45d3323..51752254 100644 --- a/packages/fpdart/test/src/extension/curry_extension_test.dart +++ b/packages/fpdart/test/src/extension/curry_extension_test.dart @@ -7,10 +7,14 @@ void main() { int subtract(int n1, int n2) => n1 - n2; int Function(int) subtractCurried(int n1) => (n2) => n1 - n2; - Glados2(any.int, any.int).test('curryAll', (n1, n2) { + Glados2(any.int, any.int).test('curry', (n1, n2) { expect(subtract(n1, n2), subtract.curry(n1)(n2)); }); + Glados2(any.int, any.int).test('curryLast', (n1, n2) { + expect(subtract(n1, n2), subtract.curryLast(n2)(n1)); + }); + Glados2(any.int, any.int).test('uncurry', (n1, n2) { expect(subtractCurried(n1)(n2), subtractCurried.uncurry(n1, n2)); }); @@ -26,6 +30,11 @@ void main() { expect(subtract(n1, n2, n3), subtract.curry(n1)(n2, n3)); }); + Glados3(any.int, any.int, any.int).test('curryLast', + (n1, n2, n3) { + expect(subtract(n1, n2, n3), subtract.curryLast(n3)(n1, n2)); + }); + Glados3(any.int, any.int, any.int).test('curryAll', (n1, n2, n3) { expect(subtract(n1, n2, n3), subtract.curryAll(n1)(n2)(n3)); @@ -49,6 +58,11 @@ void main() { expect(subtract(n1, n2, n3, n1), subtract.curry(n1)(n2, n3, n1)); }); + Glados3(any.int, any.int, any.int).test('curryLast', + (n1, n2, n3) { + expect(subtract(n1, n2, n3, n2), subtract.curryLast(n2)(n1, n2, n3)); + }); + Glados3(any.int, any.int, any.int).test('curryAll', (n1, n2, n3) { expect(subtract(n1, n2, n3, n1), subtract.curryAll(n1)(n2)(n3)(n1)); @@ -74,6 +88,12 @@ void main() { subtract(n1, n2, n3, n1, n2), subtract.curry(n1)(n2, n3, n1, n2)); }); + Glados3(any.int, any.int, any.int).test('curryLast', + (n1, n2, n3) { + expect(subtract(n1, n2, n3, n1, n3), + subtract.curryLast(n3)(n1, n2, n3, n1)); + }); + Glados3(any.int, any.int, any.int).test('curryAll', (n1, n2, n3) { expect(subtract(n1, n2, n3, n1, n2), diff --git a/packages/fpdart/test/src/function_test.dart b/packages/fpdart/test/src/function_test.dart index 0e9439c8..8093408c 100644 --- a/packages/fpdart/test/src/function_test.dart +++ b/packages/fpdart/test/src/function_test.dart @@ -59,7 +59,7 @@ void main() { Glados(any.letterOrDigits).test('toBoolEither (same as extension)', (stringValue) { expect(stringValue.toBoolEither(() => "left"), - toBoolEither(() => "left")(stringValue)); + toBoolEither(stringValue, () => "left")); }); }); }); From c1b9802baf4f7fc509899be9f6103775d9cc874a Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 27 May 2023 10:54:11 +0200 Subject: [PATCH 65/71] beta notice --- packages/fpdart/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index c8ba6e37..c9e94866 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -31,6 +31,8 @@ All the main functional programming types and patterns fully documented< ## Introduction +> **Note**: This is a Beta release for `fpdart` v1.0.0. Please [provide feedback](https://github.com/SandroMaglione/fpdart/issues/108) and [report any issue](https://github.com/SandroMaglione/fpdart/issues) to help the development of the package 🙏🏼 + > **Fpdart is fully documented. You do not need to have any previous experience with functional programming to start using `fpdart`. Give it a try!** Fpdart is inspired by [fp-ts](https://gcanti.github.io/fp-ts/), [cats](https://typelevel.org/cats/typeclasses.html#type-classes-in-cats), and [dartz](https://github.com/spebbe/dartz). From 4d62739c0302e83444fcfd136d9d3f260bbfe2a3 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 1 Jun 2023 17:08:09 +0200 Subject: [PATCH 66/71] update README links --- packages/fpdart/README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index c9e94866..b7a68026 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -110,27 +110,27 @@ dependencies: ## ✨ Examples -### [Pokeapi](./examples/pokeapi_functional/) +### [Pokeapi](/examples/pokeapi_functional/) Flutter app that lets you search and view your favorite Pokemon: - API request - Response validation - JSON conversion - State management ([riverpod](https://pub.dev/packages/riverpod)) -### [Open Meteo API](./examples/open_meteo_api/) +### [Open Meteo API](/examples/open_meteo_api/) Re-implementation using `fpdart` and functional programming of the [Open Meteo API](https://github.com/felangel/bloc/tree/master/examples/flutter_weather/packages/open_meteo_api) from the [flutter_weather](https://bloclibrary.dev/#/flutterweathertutorial) app example in the [bloc](https://pub.dev/packages/bloc) package. A 2 parts series explains step by step the Open Meteo API code: - [Open Meteo API - Functional programming with fpdart (Part 1)](https://www.sandromaglione.com/techblog/real_example_fpdart_open_meteo_api_part_1) - [Open Meteo API - Functional programming with fpdart (Part 2)](https://www.sandromaglione.com/techblog/real_example_fpdart_open_meteo_api_part_2) -### [Read/Write local file](./examples/read_write_file/) +### [Read/Write local file](/examples/read_write_file/) Example of how to read and write a local file using functional programming. -### [Manage imports](./examples/managing_imports) +### [Manage imports](/examples/managing_imports) Using `fpdart` with other libraries and noticing naming conflicts? Learn how to rename the classes that conflict with other SDK or third-party packages. -### [Option](./packages/fpdart/lib/src/option.dart) +### [Option](/packages/fpdart/lib/src/option.dart) Used when a return value can be missing. > For example, when parsing a `String` to `int`, since not all `String` > can be converted to `int` @@ -170,7 +170,7 @@ final flatMap = option.flatMap((a) => Option.of(a + 10)); final tryCatch = Option.tryCatch(() => int.parse('invalid')); ``` -### [Either](./packages/fpdart/lib/src/either.dart) +### [Either](/packages/fpdart/lib/src/either.dart) Used to handle errors (instead of `Exception`s). > `Either`: `L` is the type of the error (for example a `String` explaining > the problem), `R` is the return type when the computation is successful @@ -217,7 +217,7 @@ final dartMatch = switch (right) { final option = right.toOption(); ``` -### [IO](./packages/fpdart/lib/src/io.dart) +### [IO](/packages/fpdart/lib/src/io.dart) Wrapper around an **sync** function. Allows to compose synchronous functions **that never fail**. ```dart @@ -237,7 +237,7 @@ final int value = io.run(); final flatMap = io.flatMap((a) => IO.of(a + 10)); ``` -### [Task](./packages/fpdart/lib/src/task.dart) +### [Task](/packages/fpdart/lib/src/task.dart) Wrapper around an **async** function (`Future`). Allows to compose asynchronous functions **that never fail**. > If you look closely, it's the same as [`IO`](#io) but for **async functions** 💡 @@ -262,23 +262,23 @@ final flatMap = task.flatMap((a) => Task.of(a + 10)); ### Utility types These types compose together the 4 above ([`Option`](#option), [`Either`](#either), [`IO`](#io), [`Task`](#task)) to join together their functionalities: -- [`IOOption`](./packages/fpdart/lib/src/io_option.dart): sync function (`IO`) that may may miss the return value (`Option`) -- [`IOEither`](./packages/fpdart/lib/src/io_either.dart): sync function (`IO`) that may fail (`Either`) -- [`TaskOption`](./packages/fpdart/lib/src/task_option.dart): async function (`Task`) that may miss the return value (`Option`) -- [`TaskEither`](./packages/fpdart/lib/src/task_either.dart): async function (`Task`) that may fail (`Either`) +- [`IOOption`](/packages/fpdart/lib/src/io_option.dart): sync function (`IO`) that may may miss the return value (`Option`) +- [`IOEither`](/packages/fpdart/lib/src/io_either.dart): sync function (`IO`) that may fail (`Either`) +- [`TaskOption`](/packages/fpdart/lib/src/task_option.dart): async function (`Task`) that may miss the return value (`Option`) +- [`TaskEither`](/packages/fpdart/lib/src/task_either.dart): async function (`Task`) that may fail (`Either`) -### [Reader](./packages/fpdart/lib/src/reader.dart) -Read values from a **context** without explicitly passing the dependency between multiple nested function calls. View the [example folder for an explained usecase example](./examples/src/reader). +### [Reader](/packages/fpdart/lib/src/reader.dart) +Read values from a **context** without explicitly passing the dependency between multiple nested function calls. View the [example folder for an explained usecase example](/packages/fpdart/example/src/reader). -#### [ReaderTask](./lib/src/reader_task.dart) +#### [ReaderTask](/packages/fpdart/lib/src/reader_task.dart) Combine the `Reader` type (dependecy) with `Task` (asynchronous). -#### [ReaderTaskEither](./lib/src/reader_task_either.dart) +#### [ReaderTaskEither](/packages/fpdart/lib/src/reader_task_either.dart) Combine the `Reader` type (dependecy) with `Task` (asynchronous) and `Either` (error handling). -### [State](./lib/src/state.dart) -Used to **store**, **update**, and **extract** state in a functional way. View the [example folder for an explained usecase example](./examples/src/state). +### [State](/packages/fpdart/lib/src/state.dart) +Used to **store**, **update**, and **extract** state in a functional way. View the [example folder for an explained usecase example](/packages/fpdart/example/src/state). ### 🔗 Do notation Version `v0.6.0` introduced the **Do notation** in `fpdart`. Using the Do notation makes chaining functions easier. From 24b68071db7fe031a7c1cca3ebcef0bf11f26c9d Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 7 Jun 2023 20:28:19 +0200 Subject: [PATCH 67/71] testing hello_world example --- .../hello_world/bin/fpdart_hello_world.dart | 66 +++++++++++++++++++ examples/hello_world/bin/hello_world.dart | 7 ++ examples/hello_world/pubspec.yaml | 19 ++++++ .../test/fpdart_hello_world_test.dart | 22 +++++++ .../hello_world/test/hello_world_test.dart | 13 ++++ 5 files changed, 127 insertions(+) create mode 100644 examples/hello_world/bin/fpdart_hello_world.dart create mode 100644 examples/hello_world/bin/hello_world.dart create mode 100644 examples/hello_world/pubspec.yaml create mode 100644 examples/hello_world/test/fpdart_hello_world_test.dart create mode 100644 examples/hello_world/test/hello_world_test.dart diff --git a/examples/hello_world/bin/fpdart_hello_world.dart b/examples/hello_world/bin/fpdart_hello_world.dart new file mode 100644 index 00000000..c4ec52dd --- /dev/null +++ b/examples/hello_world/bin/fpdart_hello_world.dart @@ -0,0 +1,66 @@ +import 'package:fpdart/fpdart.dart'; + +void helloWorld(String message) { + print("Hello World: $message"); +} + +/// 1️⃣ Pure function (Thunk) +void Function() helloWorld1(String message) => () { + print("Hello World: $message"); + }; + +/// A thunk with no error is called [IO] in `fpdart` +IO helloWorld1Fpdart(String message) => IO(() { + print("Hello World: $message"); + }); + +/// 2️⃣ Explicit error +/// Understand from the return type if and how the function may fail +Either Function() helloWorld2(String message) => () { + print("Hello World: $message"); + return Either.of(null); + }; + +/// A thunk with explicit error [Either] is called [IOEither] in `fpdart` +IOEither helloWorld2Fpdart1(String message) => IOEither(() { + print("Hello World: $message"); + return Either.of(null); + }); + +/// ...or using the `right` constructor +IOEither helloWorld2Fpdart2(String message) => IOEither.right(() { + print("Hello World: $message"); + }); + +/// 3️⃣ Explicit dependency +/// Provide the `print` method as a dependency instead of implicit global function + +abstract class Console { + void log(Object? object); +} + +class ConsoleImpl implements Console { + @override + void log(Object? object) { + print(object); + } +} + +Either Function() Function(Console) helloWorld3(String message) => + (console) => () { + console.log("Hello World: $message"); + return Either.of(null); + }; + +/// Thunk (async) + error + dependency is called [ReaderTaskEither] in `fpdart` +ReaderTaskEither helloWorld3Fpdart(String message) => + ReaderTaskEither((console) async { + console.log("Hello World: $message"); + return Either.of(null); + }); + +void main(List args) { + final definition = helloWorld3("Sandro"); + final thunk = definition(ConsoleImpl()); + thunk(); +} diff --git a/examples/hello_world/bin/hello_world.dart b/examples/hello_world/bin/hello_world.dart new file mode 100644 index 00000000..4dc74cea --- /dev/null +++ b/examples/hello_world/bin/hello_world.dart @@ -0,0 +1,7 @@ +void helloWorld(String message) { + print("Hello World: $message"); +} + +void main(List args) { + helloWorld("Sandro"); +} diff --git a/examples/hello_world/pubspec.yaml b/examples/hello_world/pubspec.yaml new file mode 100644 index 00000000..7a3a050e --- /dev/null +++ b/examples/hello_world/pubspec.yaml @@ -0,0 +1,19 @@ +name: fpdart_hello_world +publish_to: none +version: 0.1.0 +homepage: https://www.sandromaglione.com/ +repository: https://github.com/SandroMaglione/fpdart +description: Example of Functional programming in Dart and Flutter using fpdart. Write a simple "Hello World" using fpdart. +author: Maglione Sandro + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + fpdart: + path: ../../packages/fpdart + +dev_dependencies: + lint: ^2.1.2 + test: ^1.24.3 + mocktail: ^0.3.0 diff --git a/examples/hello_world/test/fpdart_hello_world_test.dart b/examples/hello_world/test/fpdart_hello_world_test.dart new file mode 100644 index 00000000..686c853e --- /dev/null +++ b/examples/hello_world/test/fpdart_hello_world_test.dart @@ -0,0 +1,22 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../bin/fpdart_hello_world.dart'; + +class ConsoleTest extends Mock implements Console {} + +void main() { + group('helloWorld3Fpdart', () { + test( + 'should call "log" from the "Console" dependency with the correct input', + () async { + final console = ConsoleTest(); + const input = "test"; + + when(() => console.log(any)).thenReturn(null); + await helloWorld3Fpdart(input).run(console); + verify(() => console.log("Hello World: $input")).called(1); + }, + ); + }); +} diff --git a/examples/hello_world/test/hello_world_test.dart b/examples/hello_world/test/hello_world_test.dart new file mode 100644 index 00000000..e0515e33 --- /dev/null +++ b/examples/hello_world/test/hello_world_test.dart @@ -0,0 +1,13 @@ +import 'package:test/test.dart'; + +void main() { + group('helloWorld', () { + /// `'should call "print" with the correct input'` + /// + /// This is difficult to test, since `print` is an implicit dependency 🙌 + /// + /// Furthermore, `print` will be executed at every test. Imagine having a + /// request to update a production database instead of `print` (both are side-effects), + /// you do not want to interact with a real database with tests ⚠️ + }); +} From 7f91b90f7b799d843ed60d9d19aa3d127a8eb030 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 26 Jul 2023 05:31:13 +0200 Subject: [PATCH 68/71] updated Do notation to use _ instead of $ --- .../fpdart/open_meteo_api_client_fpdart.dart | 16 ++++---- .../lib/api/fetch_pokemon.dart | 12 +++--- examples/read_write_file/main.dart | 6 +-- packages/fpdart/CHANGELOG.md | 12 +++--- packages/fpdart/README.md | 12 +++--- packages/fpdart/example/do_notation/main.dart | 26 ++++++------- .../src/either/shopping/functional.dart | 20 +++++----- .../src/option/shopping/functional.dart | 20 +++++----- .../src/reader_task_either/overview.dart | 8 ++-- packages/fpdart/test/src/either_test.dart | 34 ++++++++--------- packages/fpdart/test/src/io_either_test.dart | 34 ++++++++--------- packages/fpdart/test/src/io_option_test.dart | 34 ++++++++--------- packages/fpdart/test/src/io_test.dart | 14 +++---- packages/fpdart/test/src/option_test.dart | 38 +++++++++---------- .../test/src/reader_task_either_test.dart | 34 ++++++++--------- .../fpdart/test/src/reader_task_test.dart | 14 +++---- .../fpdart/test/src/task_either_test.dart | 34 ++++++++--------- .../fpdart/test/src/task_option_test.dart | 34 ++++++++--------- packages/fpdart/test/src/task_test.dart | 14 +++---- 19 files changed, 208 insertions(+), 208 deletions(-) diff --git a/examples/open_meteo_api/lib/src/fpdart/open_meteo_api_client_fpdart.dart b/examples/open_meteo_api/lib/src/fpdart/open_meteo_api_client_fpdart.dart index 8ffa6ac3..edc4b4a0 100644 --- a/examples/open_meteo_api/lib/src/fpdart/open_meteo_api_client_fpdart.dart +++ b/examples/open_meteo_api/lib/src/fpdart/open_meteo_api_client_fpdart.dart @@ -29,19 +29,19 @@ class OpenMeteoApiClientFpdart { ), LocationHttpRequestFpdartFailure.new, ).chainEither( - (response) => Either.Do(($) { - final body = $( + (response) => Either.Do((_) { + final body = _( _validResponseBody(response, LocationRequestFpdartFailure.new), ); - final json = $( + final json = _( Either.tryCatch( () => jsonDecode(body), (_, __) => LocationInvalidJsonDecodeFpdartFailure(body), ), ); - final data = $( + final data = _( Either>.safeCast( json, @@ -49,24 +49,24 @@ class OpenMeteoApiClientFpdart { ), ); - final currentWeather = $( + final currentWeather = _( data .lookup('results') .toEither(LocationKeyNotFoundFpdartFailure.new), ); - final results = $( + final results = _( Either>.safeCast( currentWeather, LocationInvalidListFpdartFailure.new, ), ); - final weather = $( + final weather = _( results.head.toEither(LocationDataNotFoundFpdartFailure.new), ); - return $( + return _( Either.tryCatch( () => Location.fromJson(weather as Map), LocationFormattingFpdartFailure.new, diff --git a/examples/pokeapi_functional/lib/api/fetch_pokemon.dart b/examples/pokeapi_functional/lib/api/fetch_pokemon.dart index aa61f907..392878c1 100644 --- a/examples/pokeapi_functional/lib/api/fetch_pokemon.dart +++ b/examples/pokeapi_functional/lib/api/fetch_pokemon.dart @@ -51,17 +51,17 @@ TaskEither fetchPokemon(int pokemonId) => TaskEither.tryCatch( /// /// All the functions are simply chained together following the principle of composability. TaskEither fetchPokemonFromUserInput(String pokemonId) => - TaskEither.Do(($) async { - final validPokemonId = await $(_validateUserPokemonId( + TaskEither.Do((_) async { + final validPokemonId = await _(_validateUserPokemonId( pokemonId, ).toTaskEither()); - return $(fetchPokemon(validPokemonId)); + return _(fetchPokemon(validPokemonId)); }); -TaskEither fetchRandomPokemon = TaskEither.Do(($) async { - final pokemonId = await $(randomInt( +TaskEither fetchRandomPokemon = TaskEither.Do((_) async { + final pokemonId = await _(randomInt( Constants.minimumPokemonId, Constants.maximumPokemonId + 1, ).toTaskEither()); - return $(fetchPokemon(pokemonId)); + return _(fetchPokemon(pokemonId)); }); diff --git a/examples/read_write_file/main.dart b/examples/read_write_file/main.dart index 8f511adb..a1d364ff 100644 --- a/examples/read_write_file/main.dart +++ b/examples/read_write_file/main.dart @@ -55,9 +55,9 @@ Iterable collectFoundWords( void main() async { final collectDoNotation = TaskEither>.Do( - ($) async { - final linesIta = await $(readFileAsync('./assets/source_ita.txt')); - final linesEng = await $(readFileAsync('./assets/source_eng.txt')); + (_) async { + final linesIta = await _(readFileAsync('./assets/source_ita.txt')); + final linesEng = await _(readFileAsync('./assets/source_eng.txt')); final linesZip = linesIta.zip(linesEng); return collectFoundWords(linesZip); }, diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index f3e88654..36cac726 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -293,13 +293,13 @@ String goShopping() => goToShoppingCenter() ```dart /// Using the Do notation String goShoppingDo() => Option.Do( - ($) { - final market = $(goToShoppingCenter().alt(goToLocalMarket)); - final amount = $(market.buyAmount()); + (_) { + final market = _(goToShoppingCenter().alt(goToLocalMarket)); + final amount = _(market.buyAmount()); - final banana = $(market.buyBanana()); - final apple = $(market.buyApple()); - final pear = $(market.buyPear()); + final banana = _(market.buyBanana()); + final apple = _(market.buyApple()); + final pear = _(market.buyPear()); return 'Shopping: $banana, $apple, $pear'; }, diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index b7a68026..668bb480 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -310,13 +310,13 @@ Everything looks more **linear and simple** by using the Do notation: ```dart /// Using the Do notation String goShoppingDo() => Option.Do( - ($) { - final market = $(goToShoppingCenter().alt(goToLocalMarket)); - final amount = $(market.buyAmount()); + (_) { + final market = _(goToShoppingCenter().alt(goToLocalMarket)); + final amount = _(market.buyAmount()); - final banana = $(market.buyBanana()); - final apple = $(market.buyApple()); - final pear = $(market.buyPear()); + final banana = _(market.buyBanana()); + final apple = _(market.buyApple()); + final pear = _(market.buyPear()); return 'Shopping: $banana, $apple, $pear'; }, diff --git a/packages/fpdart/example/do_notation/main.dart b/packages/fpdart/example/do_notation/main.dart index 8783165b..adec7179 100644 --- a/packages/fpdart/example/do_notation/main.dart +++ b/packages/fpdart/example/do_notation/main.dart @@ -29,11 +29,11 @@ TaskEither changePictureSizeFromId(int id) => /// Do notation TaskEither changePictureSizeFromIdDo(int id) => TaskEither.Do( - ($) async { - final username = await $(getUsernameFromId(id)); - final image = await $(getProfilePicture(username)); + (_) async { + final username = await _(getUsernameFromId(id)); + final image = await _(getProfilePicture(username)); final width = getPictureWidth(image); - return $(updatePictureWidth(width)); + return _(updatePictureWidth(width)); }, ); @@ -49,8 +49,8 @@ Option map() => Option.of(10) (c) => c - 4, ); -Option mapDo() => Option.Do(($) { - final a = $(Option.of(10)); +Option mapDo() => Option.Do((_) { + final a = _(Option.of(10)); final b = a + 1; final c = b * 3; return c - 4; @@ -68,16 +68,16 @@ Option flatMap() => Option.of(10) (c) => Option.of(c - 4), ); -Option flatMapDo() => Option.Do(($) { - final a = $(Option.of(10)); - final b = $(Option.of(a + 1)); - final c = $(Option.of(b * 3)); - return $(Option.of(c - 4)); +Option flatMapDo() => Option.Do((_) { + final a = _(Option.of(10)); + final b = _(Option.of(a + 1)); + final c = _(Option.of(b * 3)); + return _(Option.of(c - 4)); }); /// [andThen]: Chain [Option] without storing its value Option andThen() => Option.of(10).andThen(() => Option.of(20)); -Option andThenDo() => Option.Do(($) { - $(Option.of(10)); // Chain Option, but do not store the result +Option andThenDo() => Option.Do((_) { + _(Option.of(10)); // Chain Option, but do not store the result return 20; }); diff --git a/packages/fpdart/example/src/either/shopping/functional.dart b/packages/fpdart/example/src/either/shopping/functional.dart index 2b22d4cd..75a519e0 100644 --- a/packages/fpdart/example/src/either/shopping/functional.dart +++ b/packages/fpdart/example/src/either/shopping/functional.dart @@ -41,13 +41,13 @@ String goShopping() => goToShoppingCenter() // Combine all the instructions and go shopping! 🛒 String goShoppingDo() => Either.Do( - ($) { - final market = $(goToShoppingCenter().alt(goToLocalMarket)); - final amount = $(market.buyAmount()); + (_) { + final market = _(goToShoppingCenter().alt(goToLocalMarket)); + final amount = _(market.buyAmount()); - final banana = $(market.buyBanana()); - final apple = $(market.buyApple()); - final pear = $(market.buyPear()); + final banana = _(market.buyBanana()); + final apple = _(market.buyApple()); + final pear = _(market.buyPear()); return 'Shopping: $banana, $apple, $pear'; }, @@ -59,10 +59,10 @@ String goShoppingDoFlatMap() => goToShoppingCenter() .flatMap( /// Not required types here, since [Left] inferred from chain, /// and [Right] from the return type of `Do` - (market) => Either.Do(($) { - final banana = $(market.buyBanana()); - final apple = $(market.buyApple()); - final pear = $(market.buyPear()); + (market) => Either.Do((_) { + final banana = _(market.buyBanana()); + final apple = _(market.buyApple()); + final pear = _(market.buyPear()); return 'Shopping: $banana, $apple, $pear'; }), ) diff --git a/packages/fpdart/example/src/option/shopping/functional.dart b/packages/fpdart/example/src/option/shopping/functional.dart index a8146c60..d6d10293 100644 --- a/packages/fpdart/example/src/option/shopping/functional.dart +++ b/packages/fpdart/example/src/option/shopping/functional.dart @@ -41,13 +41,13 @@ String goShopping() => goToShoppingCenter() // Combine all the instructions and go shopping! 🛒 String goShoppingDo() => Option.Do( - ($) { - final market = $(goToShoppingCenter().alt(goToLocalMarket)); - final amount = $(market.buyAmount()); + (_) { + final market = _(goToShoppingCenter().alt(goToLocalMarket)); + final amount = _(market.buyAmount()); - final banana = $(market.buyBanana()); - final apple = $(market.buyApple()); - final pear = $(market.buyPear()); + final banana = _(market.buyBanana()); + final apple = _(market.buyApple()); + final pear = _(market.buyPear()); return 'Shopping: $banana, $apple, $pear'; }, @@ -59,10 +59,10 @@ String goShoppingDo() => Option.Do( String goShoppingDoFlatMap() => goToShoppingCenter() .alt(goToLocalMarket) .flatMap( - (market) => Option.Do(($) { - final banana = $(market.buyBanana()); - final apple = $(market.buyApple()); - final pear = $(market.buyPear()); + (market) => Option.Do((_) { + final banana = _(market.buyBanana()); + final apple = _(market.buyApple()); + final pear = _(market.buyPear()); return 'Shopping: $banana, $apple, $pear'; }), ) diff --git a/packages/fpdart/example/src/reader_task_either/overview.dart b/packages/fpdart/example/src/reader_task_either/overview.dart index 859669bd..1169079f 100644 --- a/packages/fpdart/example/src/reader_task_either/overview.dart +++ b/packages/fpdart/example/src/reader_task_either/overview.dart @@ -5,15 +5,15 @@ typedef Error = String; typedef Success = String; void main(List args) async { - final rte = ReaderTaskEither.Do(($) async { + final rte = ReaderTaskEither.Do((_) async { final a = 10; - final val = await $(ReaderTaskEither.fromReader( + final val = await _(ReaderTaskEither.fromReader( Reader( (env) => env.$1 + env.$2.length, ), )); - final env = await $(ReaderTaskEither.ask()); - final env2 = await $(ReaderTaskEither.asks((dep) => dep.$2)); + final env = await _(ReaderTaskEither.ask()); + final env2 = await _(ReaderTaskEither.asks((dep) => dep.$2)); return "$a and $val and $env and $env2"; }); diff --git a/packages/fpdart/test/src/either_test.dart b/packages/fpdart/test/src/either_test.dart index 81dce2db..2c23c5c8 100644 --- a/packages/fpdart/test/src/either_test.dart +++ b/packages/fpdart/test/src/either_test.dart @@ -1145,16 +1145,16 @@ void main() { group('Do Notation', () { test('should return the correct value', () { - final doEither = Either.Do(($) => $(Either.of(10))); + final doEither = Either.Do((_) => _(Either.of(10))); doEither.matchTestRight((t) { expect(t, 10); }); }); test('should extract the correct values', () { - final doEither = Either.Do(($) { - final a = $(Either.of(10)); - final b = $(Either.of(5)); + final doEither = Either.Do((_) { + final a = _(Either.of(10)); + final b = _(Either.of(5)); return a + b; }); doEither.matchTestRight((t) { @@ -1163,10 +1163,10 @@ void main() { }); test('should return Left if any Either is Left', () { - final doEither = Either.Do(($) { - final a = $(Either.of(10)); - final b = $(Either.of(5)); - final c = $(Either.left('Error')); + final doEither = Either.Do((_) { + final a = _(Either.of(10)); + final b = _(Either.of(5)); + final c = _(Either.left('Error')); return a + b + c; }); doEither.matchTestLeft((t) { @@ -1175,8 +1175,8 @@ void main() { }); test('should rethrow if throw is used inside Do', () { - final doEither = () => Either.Do(($) { - $(Either.of(10)); + final doEither = () => Either.Do((_) { + _(Either.of(10)); throw UnimplementedError(); }); @@ -1184,8 +1184,8 @@ void main() { }); test('should rethrow if Left is thrown inside Do', () { - final doEither = () => Either.Do(($) { - $(Either.of(10)); + final doEither = () => Either.Do((_) { + _(Either.of(10)); throw Left('Error'); }); @@ -1194,9 +1194,9 @@ void main() { test('should no execute past the first Left', () { var mutable = 10; - final doEitherLeft = Either.Do(($) { - final a = $(Either.of(10)); - final b = $(Either.left("Error")); + final doEitherLeft = Either.Do((_) { + final a = _(Either.of(10)); + final b = _(Either.left("Error")); mutable += 10; return a + b; }); @@ -1206,8 +1206,8 @@ void main() { expect(l, "Error"); }); - final doEitherRight = Either.Do(($) { - final a = $(Either.of(10)); + final doEitherRight = Either.Do((_) { + final a = _(Either.of(10)); mutable += 10; return a; }); diff --git a/packages/fpdart/test/src/io_either_test.dart b/packages/fpdart/test/src/io_either_test.dart index 7d8fd9e7..d553977c 100644 --- a/packages/fpdart/test/src/io_either_test.dart +++ b/packages/fpdart/test/src/io_either_test.dart @@ -693,7 +693,7 @@ void main() { group('Do Notation', () { test('should return the correct value', () { - final doIOEither = IOEither.Do(($) => $(IOEither.of(10))); + final doIOEither = IOEither.Do((_) => _(IOEither.of(10))); final run = doIOEither.run(); run.matchTestRight((t) { expect(t, 10); @@ -701,9 +701,9 @@ void main() { }); test('should extract the correct values', () { - final doIOEither = IOEither.Do(($) { - final a = $(IOEither.of(10)); - final b = $(IOEither.of(5)); + final doIOEither = IOEither.Do((_) { + final a = _(IOEither.of(10)); + final b = _(IOEither.of(5)); return a + b; }); final run = doIOEither.run(); @@ -713,10 +713,10 @@ void main() { }); test('should return Left if any Either is Left', () { - final doIOEither = IOEither.Do(($) { - final a = $(IOEither.of(10)); - final b = $(IOEither.of(5)); - final c = $(IOEither.left('Error')); + final doIOEither = IOEither.Do((_) { + final a = _(IOEither.of(10)); + final b = _(IOEither.of(5)); + final c = _(IOEither.left('Error')); return a + b + c; }); final run = doIOEither.run(); @@ -726,8 +726,8 @@ void main() { }); test('should rethrow if throw is used inside Do', () { - final doIOEither = IOEither.Do(($) { - $(IOEither.of(10)); + final doIOEither = IOEither.Do((_) { + _(IOEither.of(10)); throw UnimplementedError(); }); @@ -735,8 +735,8 @@ void main() { }); test('should rethrow if Left is thrown inside Do', () { - final doIOEither = IOEither.Do(($) { - $(IOEither.of(10)); + final doIOEither = IOEither.Do((_) { + _(IOEither.of(10)); throw Left('Error'); }); @@ -745,9 +745,9 @@ void main() { test('should no execute past the first Left', () { var mutable = 10; - final doIOEitherLeft = IOEither.Do(($) { - final a = $(IOEither.of(10)); - final b = $(IOEither.left("Error")); + final doIOEitherLeft = IOEither.Do((_) { + final a = _(IOEither.of(10)); + final b = _(IOEither.left("Error")); mutable += 10; return a + b; }); @@ -758,8 +758,8 @@ void main() { expect(l, "Error"); }); - final doIOEitherRight = IOEither.Do(($) { - final a = $(IOEither.of(10)); + final doIOEitherRight = IOEither.Do((_) { + final a = _(IOEither.of(10)); mutable += 10; return a; }); diff --git a/packages/fpdart/test/src/io_option_test.dart b/packages/fpdart/test/src/io_option_test.dart index b29598f0..4a4cd93f 100644 --- a/packages/fpdart/test/src/io_option_test.dart +++ b/packages/fpdart/test/src/io_option_test.dart @@ -500,7 +500,7 @@ void main() { group('Do Notation', () { test('should return the correct value', () { - final doIOOption = IOOption.Do(($) => $(IOOption.of(10))); + final doIOOption = IOOption.Do((_) => _(IOOption.of(10))); final run = doIOOption.run(); run.matchTestSome((t) { expect(t, 10); @@ -508,9 +508,9 @@ void main() { }); test('should extract the correct values', () { - final doIOOption = IOOption.Do(($) { - final a = $(IOOption.of(10)); - final b = $(IOOption.of(5)); + final doIOOption = IOOption.Do((_) { + final a = _(IOOption.of(10)); + final b = _(IOOption.of(5)); return a + b; }); final run = doIOOption.run(); @@ -520,10 +520,10 @@ void main() { }); test('should return Left if any Either is Left', () { - final doIOOption = IOOption.Do(($) { - final a = $(IOOption.of(10)); - final b = $(IOOption.of(5)); - final c = $(IOOption.none()); + final doIOOption = IOOption.Do((_) { + final a = _(IOOption.of(10)); + final b = _(IOOption.of(5)); + final c = _(IOOption.none()); return a + b + c; }); final run = doIOOption.run(); @@ -531,8 +531,8 @@ void main() { }); test('should rethrow if throw is used inside Do', () { - final doIOOption = IOOption.Do(($) { - $(IOOption.of(10)); + final doIOOption = IOOption.Do((_) { + _(IOOption.of(10)); throw UnimplementedError(); }); @@ -541,8 +541,8 @@ void main() { }); test('should rethrow if None is thrown inside Do', () { - final doIOOption = IOOption.Do(($) { - $(IOOption.of(10)); + final doIOOption = IOOption.Do((_) { + _(IOOption.of(10)); throw const None(); }); @@ -551,9 +551,9 @@ void main() { test('should no execute past the first Left', () { var mutable = 10; - final doIOOptionNone = IOOption.Do(($) { - final a = $(IOOption.of(10)); - final b = $(IOOption.none()); + final doIOOptionNone = IOOption.Do((_) { + final a = _(IOOption.of(10)); + final b = _(IOOption.none()); mutable += 10; return a + b; }); @@ -562,8 +562,8 @@ void main() { expect(mutable, 10); expect(runNone, isA()); - final doIOOptionSome = IOOption.Do(($) { - final a = $(IOOption.of(10)); + final doIOOptionSome = IOOption.Do((_) { + final a = _(IOOption.of(10)); mutable += 10; return a; }); diff --git a/packages/fpdart/test/src/io_test.dart b/packages/fpdart/test/src/io_test.dart index ad094397..30b52f64 100644 --- a/packages/fpdart/test/src/io_test.dart +++ b/packages/fpdart/test/src/io_test.dart @@ -189,15 +189,15 @@ void main() { group('Do Notation', () { test('should return the correct value', () { - final doIO = IO.Do(($) => $(IO.of(10))); + final doIO = IO.Do((_) => _(IO.of(10))); final run = doIO.run(); expect(run, 10); }); test('should extract the correct values', () { - final doIO = IO.Do(($) { - final a = $(IO.of(10)); - final b = $(IO.of(5)); + final doIO = IO.Do((_) { + final a = _(IO.of(10)); + final b = _(IO.of(5)); return a + b; }); final run = doIO.run(); @@ -206,9 +206,9 @@ void main() { test('should not execute until run is called', () { var mutable = 10; - final doIO = IO.Do(($) { - final a = $(IO.of(10)); - final b = $(IO.of(5)); + final doIO = IO.Do((_) { + final a = _(IO.of(10)); + final b = _(IO.of(5)); mutable += 10; return a + b; }); diff --git a/packages/fpdart/test/src/option_test.dart b/packages/fpdart/test/src/option_test.dart index 543d0572..c3ca0ef8 100644 --- a/packages/fpdart/test/src/option_test.dart +++ b/packages/fpdart/test/src/option_test.dart @@ -708,16 +708,16 @@ void main() { group('Do Notation', () { test('should return the correct value', () { - final doOption = Option.Do(($) => $(Option.of(10))); + final doOption = Option.Do((_) => _(Option.of(10))); doOption.matchTestSome((t) { expect(t, 10); }); }); test('should extract the correct values', () { - final doOption = Option.Do(($) { - final a = $(Option.of(10)); - final b = $(Option.of(5)); + final doOption = Option.Do((_) { + final a = _(Option.of(10)); + final b = _(Option.of(5)); return a + b; }); doOption.matchTestSome((t) { @@ -726,10 +726,10 @@ void main() { }); test('should return None if any Option is None', () { - final doOption = Option.Do(($) { - final a = $(Option.of(10)); - final b = $(Option.of(5)); - final c = $(Option.none()); + final doOption = Option.Do((_) { + final a = _(Option.of(10)); + final b = _(Option.of(5)); + final c = _(Option.none()); return a + b + c; }); @@ -737,8 +737,8 @@ void main() { }); test('should rethrow if throw is used inside Do', () { - final doOption = () => Option.Do(($) { - $(Option.of(10)); + final doOption = () => Option.Do((_) { + _(Option.of(10)); throw UnimplementedError(); }); @@ -746,8 +746,8 @@ void main() { }); test('should rethrow if None is thrown inside Do', () { - final doOption = () => Option.Do(($) { - $(Option.of(10)); + final doOption = () => Option.Do((_) { + _(Option.of(10)); throw None(); }); @@ -755,8 +755,8 @@ void main() { }); test('should throw if the error is not None', () { - final doOption = () => Option.Do(($) { - $(Option.of(10)); + final doOption = () => Option.Do((_) { + _(Option.of(10)); throw UnimplementedError(); }); @@ -765,9 +765,9 @@ void main() { test('should no execute past the first None', () { var mutable = 10; - final doOptionNone = Option.Do(($) { - final a = $(Option.of(10)); - final b = $(Option.none()); + final doOptionNone = Option.Do((_) { + final a = _(Option.of(10)); + final b = _(Option.none()); mutable += 10; return a + b; }); @@ -775,8 +775,8 @@ void main() { expect(mutable, 10); expect(doOptionNone, isA()); - final doOptionSome = Option.Do(($) { - final a = $(Option.of(10)); + final doOptionSome = Option.Do((_) { + final a = _(Option.of(10)); mutable += 10; return a; }); diff --git a/packages/fpdart/test/src/reader_task_either_test.dart b/packages/fpdart/test/src/reader_task_either_test.dart index abbe55e2..b2f9569f 100644 --- a/packages/fpdart/test/src/reader_task_either_test.dart +++ b/packages/fpdart/test/src/reader_task_either_test.dart @@ -834,7 +834,7 @@ void main() { group('Do Notation', () { test('should return the correct value', () async { final doTaskEither = ReaderTaskEither.Do( - ($) => $( + (_) => _( ReaderTaskEither.asks((env) => env.toInt()), ), ); @@ -847,9 +847,9 @@ void main() { test('should extract the correct values', () async { final doTaskEither = - ReaderTaskEither.Do(($) async { - final a = await $(ReaderTaskEither.of(10)); - final b = await $(ReaderTaskEither.asks((env) => env.toInt())); + ReaderTaskEither.Do((_) async { + final a = await _(ReaderTaskEither.of(10)); + final b = await _(ReaderTaskEither.asks((env) => env.toInt())); return a + b; }); @@ -861,10 +861,10 @@ void main() { test('should return Left if any Either is Left', () async { final doTaskEither = - ReaderTaskEither.Do(($) async { - final a = await $(ReaderTaskEither.of(10)); - final b = await $(ReaderTaskEither.asks((env) => env.toInt())); - final c = await $( + ReaderTaskEither.Do((_) async { + final a = await _(ReaderTaskEither.of(10)); + final b = await _(ReaderTaskEither.asks((env) => env.toInt())); + final c = await _( ReaderTaskEither.left('error'), ); @@ -878,8 +878,8 @@ void main() { }); test('should rethrow if throw is used inside Do', () { - final doTaskEither = ReaderTaskEither.Do(($) { - $(ReaderTaskEither.of(10)); + final doTaskEither = ReaderTaskEither.Do((_) { + _(ReaderTaskEither.of(10)); throw UnimplementedError(); }); @@ -892,8 +892,8 @@ void main() { }); test('should rethrow if Left is thrown inside Do', () { - final doTaskEither = ReaderTaskEither.Do(($) { - $( + final doTaskEither = ReaderTaskEither.Do((_) { + _( ReaderTaskEither.of(10), ); throw Left('error'); @@ -911,10 +911,10 @@ void main() { var mutable = 10; final doTaskEitherLeft = - ReaderTaskEither.Do(($) async { - final a = await $(ReaderTaskEither.of(10)); + ReaderTaskEither.Do((_) async { + final a = await _(ReaderTaskEither.of(10)); final b = - await $(ReaderTaskEither.left("error")); + await _(ReaderTaskEither.left("error")); mutable += 10; return a + b; }); @@ -926,8 +926,8 @@ void main() { }); final doTaskEitherRight = - ReaderTaskEither.Do(($) async { - final a = await $(ReaderTaskEither.asks((env) => env.toInt())); + ReaderTaskEither.Do((_) async { + final a = await _(ReaderTaskEither.asks((env) => env.toInt())); mutable += 10; return a; }); diff --git a/packages/fpdart/test/src/reader_task_test.dart b/packages/fpdart/test/src/reader_task_test.dart index 8b95eb07..0ae274f9 100644 --- a/packages/fpdart/test/src/reader_task_test.dart +++ b/packages/fpdart/test/src/reader_task_test.dart @@ -189,7 +189,7 @@ void main() { group('Do Notation', () { test('should return the correct value', () async { final doTask = ReaderTask.Do( - ($) => $( + (_) => _( ReaderTask.of(10), ), ); @@ -199,9 +199,9 @@ void main() { }); test('should extract the correct values', () async { - final doTask = ReaderTask.Do(($) async { - final a = await $(ReaderTask((env) async => env.length)); - final b = await $(ReaderTask((env) async => env.length)); + final doTask = ReaderTask.Do((_) async { + final a = await _(ReaderTask((env) async => env.length)); + final b = await _(ReaderTask((env) async => env.length)); return a + b; }); @@ -211,9 +211,9 @@ void main() { test('should not execute until run is called', () async { var mutable = 10; - final doTask = ReaderTask.Do(($) async { - final a = await $(ReaderTask.of(10)); - final b = await $(ReaderTask.of(5)); + final doTask = ReaderTask.Do((_) async { + final a = await _(ReaderTask.of(10)); + final b = await _(ReaderTask.of(5)); mutable += 10; return a + b; }); diff --git a/packages/fpdart/test/src/task_either_test.dart b/packages/fpdart/test/src/task_either_test.dart index b145a37c..2e45e7dc 100644 --- a/packages/fpdart/test/src/task_either_test.dart +++ b/packages/fpdart/test/src/task_either_test.dart @@ -944,7 +944,7 @@ void main() { group('Do Notation', () { test('should return the correct value', () async { final doTaskEither = - TaskEither.Do(($) => $(TaskEither.of(10))); + TaskEither.Do((_) => _(TaskEither.of(10))); final run = await doTaskEither.run(); run.matchTestRight((t) { expect(t, 10); @@ -952,9 +952,9 @@ void main() { }); test('should extract the correct values', () async { - final doTaskEither = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); - final b = await $(TaskEither.of(5)); + final doTaskEither = TaskEither.Do((_) async { + final a = await _(TaskEither.of(10)); + final b = await _(TaskEither.of(5)); return a + b; }); final run = await doTaskEither.run(); @@ -964,10 +964,10 @@ void main() { }); test('should return Left if any Either is Left', () async { - final doTaskEither = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); - final b = await $(TaskEither.of(5)); - final c = await $(TaskEither.left('Error')); + final doTaskEither = TaskEither.Do((_) async { + final a = await _(TaskEither.of(10)); + final b = await _(TaskEither.of(5)); + final c = await _(TaskEither.left('Error')); return a + b + c; }); final run = await doTaskEither.run(); @@ -977,8 +977,8 @@ void main() { }); test('should rethrow if throw is used inside Do', () { - final doTaskEither = TaskEither.Do(($) { - $(TaskEither.of(10)); + final doTaskEither = TaskEither.Do((_) { + _(TaskEither.of(10)); throw UnimplementedError(); }); @@ -987,8 +987,8 @@ void main() { }); test('should rethrow if Left is thrown inside Do', () { - final doTaskEither = TaskEither.Do(($) { - $(TaskEither.of(10)); + final doTaskEither = TaskEither.Do((_) { + _(TaskEither.of(10)); throw Left('Error'); }); @@ -997,9 +997,9 @@ void main() { test('should no execute past the first Left', () async { var mutable = 10; - final doTaskEitherLeft = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); - final b = await $(TaskEither.left("Error")); + final doTaskEitherLeft = TaskEither.Do((_) async { + final a = await _(TaskEither.of(10)); + final b = await _(TaskEither.left("Error")); mutable += 10; return a + b; }); @@ -1010,8 +1010,8 @@ void main() { expect(l, "Error"); }); - final doTaskEitherRight = TaskEither.Do(($) async { - final a = await $(TaskEither.of(10)); + final doTaskEitherRight = TaskEither.Do((_) async { + final a = await _(TaskEither.of(10)); mutable += 10; return a; }); diff --git a/packages/fpdart/test/src/task_option_test.dart b/packages/fpdart/test/src/task_option_test.dart index 931a2c51..0c1a727d 100644 --- a/packages/fpdart/test/src/task_option_test.dart +++ b/packages/fpdart/test/src/task_option_test.dart @@ -669,7 +669,7 @@ void main() { group('Do Notation', () { test('should return the correct value', () async { - final doTaskOption = TaskOption.Do(($) => $(TaskOption.of(10))); + final doTaskOption = TaskOption.Do((_) => _(TaskOption.of(10))); final run = await doTaskOption.run(); run.matchTestSome((t) { expect(t, 10); @@ -677,9 +677,9 @@ void main() { }); test('should extract the correct values', () async { - final doTaskOption = TaskOption.Do(($) async { - final a = await $(TaskOption.of(10)); - final b = await $(TaskOption.of(5)); + final doTaskOption = TaskOption.Do((_) async { + final a = await _(TaskOption.of(10)); + final b = await _(TaskOption.of(5)); return a + b; }); final run = await doTaskOption.run(); @@ -689,10 +689,10 @@ void main() { }); test('should return Left if any Either is Left', () async { - final doTaskOption = TaskOption.Do(($) async { - final a = await $(TaskOption.of(10)); - final b = await $(TaskOption.of(5)); - final c = await $(TaskOption.none()); + final doTaskOption = TaskOption.Do((_) async { + final a = await _(TaskOption.of(10)); + final b = await _(TaskOption.of(5)); + final c = await _(TaskOption.none()); return a + b + c; }); final run = await doTaskOption.run(); @@ -700,8 +700,8 @@ void main() { }); test('should rethrow if throw is used inside Do', () { - final doTaskOption = TaskOption.Do(($) { - $(TaskOption.of(10)); + final doTaskOption = TaskOption.Do((_) { + _(TaskOption.of(10)); throw UnimplementedError(); }); @@ -710,8 +710,8 @@ void main() { }); test('should rethrow if None is thrown inside Do', () { - final doTaskOption = TaskOption.Do(($) { - $(TaskOption.of(10)); + final doTaskOption = TaskOption.Do((_) { + _(TaskOption.of(10)); throw const None(); }); @@ -720,9 +720,9 @@ void main() { test('should no execute past the first Left', () async { var mutable = 10; - final doTaskOptionNone = TaskOption.Do(($) async { - final a = await $(TaskOption.of(10)); - final b = await $(TaskOption.none()); + final doTaskOptionNone = TaskOption.Do((_) async { + final a = await _(TaskOption.of(10)); + final b = await _(TaskOption.none()); mutable += 10; return a + b; }); @@ -731,8 +731,8 @@ void main() { expect(mutable, 10); expect(runNone, isA()); - final doTaskOptionSome = TaskOption.Do(($) async { - final a = await $(TaskOption.of(10)); + final doTaskOptionSome = TaskOption.Do((_) async { + final a = await _(TaskOption.of(10)); mutable += 10; return a; }); diff --git a/packages/fpdart/test/src/task_test.dart b/packages/fpdart/test/src/task_test.dart index 8bda8137..22c78c6c 100644 --- a/packages/fpdart/test/src/task_test.dart +++ b/packages/fpdart/test/src/task_test.dart @@ -285,15 +285,15 @@ void main() { group('Do Notation', () { test('should return the correct value', () async { - final doTask = Task.Do(($) => $(Task.of(10))); + final doTask = Task.Do((_) => _(Task.of(10))); final run = await doTask.run(); expect(run, 10); }); test('should extract the correct values', () async { - final doTask = Task.Do(($) async { - final a = await $(Task.of(10)); - final b = await $(Task.of(5)); + final doTask = Task.Do((_) async { + final a = await _(Task.of(10)); + final b = await _(Task.of(5)); return a + b; }); final run = await doTask.run(); @@ -302,9 +302,9 @@ void main() { test('should not execute until run is called', () async { var mutable = 10; - final doTask = Task.Do(($) async { - final a = await $(Task.of(10)); - final b = await $(Task.of(5)); + final doTask = Task.Do((_) async { + final a = await _(Task.of(10)); + final b = await _(Task.of(5)); mutable += 10; return a + b; }); From 80fd91aae359f845f6bed128cec95ec75a0ecd16 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 26 Jul 2023 06:12:40 +0200 Subject: [PATCH 69/71] updated README --- packages/fpdart/README.md | 117 ++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index 668bb480..fdddc6a0 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -31,11 +31,9 @@ All the main functional programming types and patterns fully documented< ## Introduction -> **Note**: This is a Beta release for `fpdart` v1.0.0. Please [provide feedback](https://github.com/SandroMaglione/fpdart/issues/108) and [report any issue](https://github.com/SandroMaglione/fpdart/issues) to help the development of the package 🙏🏼 +> **fpdart is fully documented. You do not need to have any previous experience with functional programming to start using `fpdart`. Give it a try!** -> **Fpdart is fully documented. You do not need to have any previous experience with functional programming to start using `fpdart`. Give it a try!** - -Fpdart is inspired by [fp-ts](https://gcanti.github.io/fp-ts/), [cats](https://typelevel.org/cats/typeclasses.html#type-classes-in-cats), and [dartz](https://github.com/spebbe/dartz). +fpdart is inspired by [fp-ts](https://gcanti.github.io/fp-ts/), [cats](https://typelevel.org/cats/typeclasses.html#type-classes-in-cats), and [dartz](https://github.com/spebbe/dartz). > Follow my [**Twitter**](https://twitter.com/SandroMaglione) for updates, or [subscribe to the newsletter](https://www.sandromaglione.com/newsletter) @@ -43,9 +41,11 @@ Fpdart is inspired by [fp-ts](https://gcanti.github.io/fp-ts/), [cats](https://t - [Introduction](#introduction) - [📖 Learn `functional programming` and `fpdart`](#-learn-functional-programming-and-fpdart) - - [👨‍💻 Blog posts and tutorials](#-blog-posts-and-tutorials) + - [✍️ Blog posts and tutorials](#️-blog-posts-and-tutorials) + - [🧑‍🏫 Getting started with functional programming](#-getting-started-with-functional-programming) - [💻 Installation](#-installation) - [✨ Examples](#-examples) + - [`fpdart` + `riverpod`](#fpdart--riverpod) - [Pokeapi](#pokeapi) - [Open Meteo API](#open-meteo-api) - [Read/Write local file](#readwrite-local-file) @@ -79,25 +79,37 @@ Would you like to know more about functional programming, fpdart, and how to use 📚 [**Collection of tutorials on fpdart**](https://www.sandromaglione.com/course/fpdart-functional-programming-dart-and-flutter) -Check out also this series of articles about functional programming with `fpdart`: +Are you new to `fpdart` and functional programming? -1. [**Fpdart, Functional Programming in Dart and Flutter**](https://www.sandromaglione.com/fpdart-functional-programming-in-dart-and-flutter/) -2. [**How to use fpdart Functional Programming in your Dart and Flutter app**](https://www.sandromaglione.com/how-to-use-fpdart-functional-programming-in-dart-and-flutter/) -3. [**Pure Functional app in Flutter – Pokemon app using fpdart and Functional Programming**](https://www.sandromaglione.com/pure-functional-app-in-flutter-using-fpdart-functional-programming/) -4. [**Functional Programming Option type – Introduction**](https://www.sandromaglione.com/functional-programming-option-type-tutorial/) -5. [**Chain functions using Option type – Functional Programming**](https://www.sandromaglione.com/chain-functions-using-option-type-functional-programming/) -6. [**Practical Functional Programming - Part 1**](https://www.sandromaglione.com/practical-functional-programming-step-by-step-haskell-typescript-dart-part-1/) -7. [**Practical Functional Programming - Part 2**](https://www.sandromaglione.com/practical-functional-programming-pure-functions-part-2/) -8. [**Practical Functional Programming - Part 3**](https://www.sandromaglione.com/immutability-practical-functional-programming-part-3/) +👨‍💻 [**Getting started with `fpdart` complete guide**](https://www.sandromaglione.com/techblog/getting-started-with-fpdart-v1-functional-programming) -### 👨‍💻 Blog posts and tutorials -- [How to make API requests with validation in fpdart](https://www.sandromaglione.com/techblog/fpdart-api-request-with-validation-functional-programming) -- [How to use TaskEither in fpdart](https://www.sandromaglione.com/techblog/how-to-use-task-either-fpdart-functional-programming) -- [How to map an Either to a Future in fpdart](https://blog.sandromaglione.com/techblog/from-sync-to-async-functional-programming) +Interested in what `fpdart` is and how it came to be? + +🚶 [**Full history of `fpdart` and functional programming in dart**](https://www.sandromaglione.com/techblog/the-history-of-fpdart-functional-programming-in-dart) + + +### ✍️ Blog posts and tutorials - [Option type and Null Safety in dart](https://www.sandromaglione.com/techblog/option_type_and_null_safety_dart) - [Either - Error Handling in Functional Programming](https://www.sandromaglione.com/techblog/either-error-handling-functional-programming) - [Future & Task: asynchronous Functional Programming](https://www.sandromaglione.com/techblog/async-requests-future-and-task-dart) +- [How to use TaskEither in fpdart](https://www.sandromaglione.com/techblog/how-to-use-task-either-fpdart-functional-programming) +- [How to make API requests with validation in fpdart](https://www.sandromaglione.com/techblog/fpdart-api-request-with-validation-functional-programming) +- [How to map an Either to a Future in fpdart](https://blog.sandromaglione.com/techblog/from-sync-to-async-functional-programming) - [Flutter Supabase Functional Programming with fpdart](https://www.sandromaglione.com/techblog/flutter-dart-functional-programming-fpdart-supabase-app) +- [fpdart, Functional Programming in Dart and Flutter](https://www.sandromaglione.com/techblog/fpdart-functional-programming-in-dart-and-flutter/) +- [How to use fpdart Functional Programming in your Dart and Flutter app](https://www.sandromaglione.com/techblog/how-to-use-fpdart-functional-programming-in-dart-and-flutter/) +- [Pure Functional app in Flutter – Pokemon app using fpdart and Functional Programming](https://www.sandromaglione.com/techblog/pure-functional-app-in-flutter-using-fpdart-functional-programming/) + +### 🧑‍🏫 Getting started with functional programming + +- [Functional Programming Option type – Introduction](https://www.sandromaglione.com/techblog/functional-programming-option-type-tutorial/) +- [Chain functions using Option type – Functional Programming](https://www.sandromaglione.com/techblog/chain-functions-using-option-type-functional-programming/) +- [Practical Functional Programming - Find repeated characters | Part 1](https://www.sandromaglione.com/techblog/practical-functional-programming-step-by-step-haskell-typescript-dart-part-1/) +- [Pure Functions - Practical Functional Programming | Part 2](https://www.sandromaglione.com/techblog/practical-functional-programming-pure-functions-part-2/) +- [Immutability – Practical Functional Programming | Part 3](https://www.sandromaglione.com/techblog/immutability-practical-functional-programming-part-3/) +- [Loops using fold – Practical Functional Programming | Part 4](https://www.sandromaglione.com/techblog/loops-using-fold-practical-functional-programming) +- [Maybe | Practical Functional Programming](https://www.sandromaglione.com/techblog/maybe-practical-functional-programming) +- [5 Lessons from Functional Programming in Scala, By Paul Chiusano and Runar Bjarnason](https://www.sandromaglione.com/techblog/functional-programming-5-key-lessons-functional-programming-in-scala) ## 💻 Installation @@ -105,11 +117,14 @@ Check out also this series of articles about functional programming with `fpdart ```yaml # pubspec.yaml dependencies: - fpdart: ^1.0.0-beta.1 # Check out the latest version + fpdart: ^1.0.0 # Check out the latest version ``` ## ✨ Examples +### [`fpdart` + `riverpod`](https://www.sandromaglione.com/course/fpdart-riverpod-develop-flutter-app) +Step by step course on how to build a safe, maintainable, and testable Flutter app using `fpdart` and `riverpod`. + ### [Pokeapi](/examples/pokeapi_functional/) Flutter app that lets you search and view your favorite Pokemon: - API request @@ -327,7 +342,7 @@ String goShoppingDo() => Option.Do( You initialize the Do notation using the **`Do()` constructor**. -You have access to a `$` function, that you can use to extract and use the value inside each `Option`, without using `flatMap`. +You have access to a `_` function, that you can use to extract and use the value inside each `Option`, without using `flatMap`. > **Note**: We recommend using the Do notation whenever possible to improve the legibility of your code 🤝 @@ -382,29 +397,35 @@ Many more examples are coming soon. Check out [**my website**](https://www.sandr - [x] `Option` - [x] `Either` - [x] `Unit` +- [x] `IO` +- [x] `IORef` +- [x] `IOOption` +- [x] `IOEither` - [x] `Task` +- [x] `TaskOption` - [x] `TaskEither` +- [x] `Reader` +- [x] `ReaderTask` +- [x] `ReaderTaskEither` - [x] `State` - [x] `StateAsync` -- [x] `Reader` -- [x] `Tuple` -- [x] `IO` -- [x] `IORef` - [x] `Iterable` (`List`) `extension` - [x] `Map` `extension` -- [x] `IOEither` -- [x] `TaskOption` - [x] `Predicate` -- [x] `IOOption` -- [x] `ReaderTask` -- [x] `ReaderTaskEither` +- [ ] `ReaderTaskOption` +- [ ] `ReaderIO` +- [ ] `ReaderIOEither` +- [ ] `ReaderIOOption` - [ ] `ReaderEither` +- [ ] `ReaderOption` - [ ] `StateReaderTaskEither` - [ ] `Lens` - [ ] `Writer` ## 💡 Motivation +📚 [**Read the full story of the origin and motivation for `fpdart`**](https://www.sandromaglione.com/techblog/the-history-of-fpdart-functional-programming-in-dart) + Functional programming is becoming more and more popular, and for good reasons. Many non-functional languages are slowly adopting patterns from functional languages, dart included. Dart already supports higher-order functions, generic types, type inference. Since Dart 3, the language supports also [pattern matching](https://github.com/dart-lang/language/issues/546), [destructuring](https://github.com/dart-lang/language/issues/207), [multiple return values](https://github.com/dart-lang/language/issues/68) ([**Read more about these new features here**](https://www.sandromaglione.com/techblog/records-and-patterns-dart-language)). @@ -413,7 +434,7 @@ Other functional programming features are coming to the language, like [higher-o Many packages are bringing functional patterns to dart, like the amazing [freezed](https://pub.dev/packages/freezed) for unions/pattern matching. -Fpdart aims to provide all the main types found in functional languages to dart. Types like `Option` (handle missing values without `null`), `Either` (handle errors and error messages), `Task` (composable async computations), and more. +fpdart aims to provide all the main types found in functional languages to dart. Types like `Option` (handle missing values without `null`), `Either` (handle errors and error messages), `Task` (composable async computations), and more. ### Goal @@ -421,7 +442,7 @@ Differently from many other functional programming packages, `fpdart` aims to in > **You do not need to have any previous experience with functional programming to start using `fpdart`.** -Fpdart also provides [real-world examples](https://github.com/SandroMaglione/fpdart/tree/main/examples/) of why a type is useful and how it can be used in your application. Check out [**my website**](https://www.sandromaglione.com/) for blog posts and articles. +fpdart also provides [real-world examples](https://github.com/SandroMaglione/fpdart/tree/main/examples/) of why a type is useful and how it can be used in your application. Check out [**my website**](https://www.sandromaglione.com/) for blog posts and articles. ### Comparison with `dartz` @@ -431,26 +452,24 @@ One of the major pain points of dartz has always been is [**lack of documentatio `dartz` is also missing some features and types (`Reader`, `TaskEither`, and others). -Fpdart is a rewrite based on fp-ts and cats. The main differences are: +`fpdart` is a rewrite based on fp-ts and cats. The main differences are: -- Fpdart is fully documented. -- Fpdart implements higher-kinded types using [defunctionalization](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf). -- Fpdart is based on **Dart 3**. -- Fpdart is completely null-safe from the beginning. -- Fpdart has a richer API. -- Fpdart implements some missing types in dartz. -- Fpdart (currently) does not provide implementation for immutable collections (`ISet`, `IMap`, `IHashMap`, `AVLTree`). +- fpdart is fully documented. +- fpdart implements higher-kinded types using [defunctionalization](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf). +- fpdart is based on **Dart 3**. +- fpdart is completely null-safe from the beginning. +- fpdart has a richer API. +- fpdart implements some missing types in dartz. +- fpdart does not provide implementation for immutable collections (`ISet`, `IMap`, `IHashMap`, `AVLTree`). ## 🤔 Roadmap Being documentation and stability important goals of the package, every type will go through an implementation-documentation-testing cycle before being considered as _'stable'_. -The roadmap for types development is highlighted below (breaking changes to _'stable'_ types are to be expected in this early stages): - -1. `ReaderEither` -2. `StateReaderTaskEither` -3. `Writer` -4. `Lens` +The current work on `fpdart` aims to: +- Expand the API to include more methods to work and convert between types easier (send [an issue](https://github.com/SandroMaglione/fpdart/issues) on the repository if you have a suggestion) +- Simplify the current API to make it easier to use and learn +- Expand the documentation with more articles and [documentation comments](https://dart.dev/effective-dart/documentation#do-use--doc-comments-to-document-members-and-types) > **Note**: There is also an experimental research in progress to implement [`ZIO`](https://zio.dev/) in `fpdart`, stay tuned 🔜 @@ -464,7 +483,7 @@ In general, **any contribution or feedback is welcome** (and encouraged!). ## 📃 Versioning -- **v1.0.0-beta.1** - 27 May 2023 +- **v1.0.0** - 26 July 2023 *** @@ -492,9 +511,11 @@ In general, **any contribution or feedback is welcome** (and encouraged!). ## 😀 Support -Currently the best way to support me would be to follow me on my [**Twitter**](https://twitter.com/SandroMaglione). +If you are interested in my work you can [subscribe to my newsletter](https://www.sandromaglione.com/newsletter). + +I share tutorials, guides, and code snippets about `fpdart` and functional programming: [**Subscribe to the Newsletter here** 📧](https://www.sandromaglione.com/newsletter) -I also have a newsletter, in which I share tutorials, guides, and code snippets about fpdart and functional programming: [**Subscribe to the Newsletter here** 📧](https://www.sandromaglione.com/newsletter) +For more frequent updates you can also follow me on my [**Twitter**](https://twitter.com/SandroMaglione). ## 👀 License From 977db855a9d8adea0534718f622a86e2c3ca9313 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 26 Jul 2023 06:13:17 +0200 Subject: [PATCH 70/71] pubspec.yaml v1.0.0 --- packages/fpdart/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fpdart/pubspec.yaml b/packages/fpdart/pubspec.yaml index 017f801a..e91a706e 100644 --- a/packages/fpdart/pubspec.yaml +++ b/packages/fpdart/pubspec.yaml @@ -2,7 +2,7 @@ name: fpdart description: > Functional programming in Dart and Flutter. All the main functional programming types and patterns fully documented, tested, and with examples. -version: 1.0.0-beta.1 +version: 1.0.0 homepage: https://www.sandromaglione.com/ repository: https://github.com/SandroMaglione/fpdart author: Maglione Sandro From 9e90b42001ce93b65c14407c5396ce5bc3fc5a36 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 26 Jul 2023 06:16:47 +0200 Subject: [PATCH 71/71] Updated CHANGELOG --- packages/fpdart/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/fpdart/CHANGELOG.md b/packages/fpdart/CHANGELOG.md index 36cac726..2303dea5 100644 --- a/packages/fpdart/CHANGELOG.md +++ b/packages/fpdart/CHANGELOG.md @@ -262,6 +262,9 @@ final result = switch (boolValue) { true => 1, false => -1 }; - Removed `Magma` typedef ⚠️ - Removed extension methods on nullable types (`toOption`, `toEither`, `toTaskOption`, `toIOEither`, `toTaskEither`, `toTaskEitherAsync`) ⚠️ - Organized all extensions inside internal `extension` folder +- Updated [README](./README.md) + - [**Getting started with `fpdart` complete guide**](https://www.sandromaglione.com/techblog/getting-started-with-fpdart-v1-functional-programming) + - [**Full history of `fpdart` and functional programming in dart**](https://www.sandromaglione.com/techblog/the-history-of-fpdart-functional-programming-in-dart) ***