From 8208edc144908c0ea4880f6e187b4d11e4363812 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 19 Oct 2022 06:48:21 +0200 Subject: [PATCH 01/12] init extension on nullable values --- lib/src/nullable_extension.dart | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 lib/src/nullable_extension.dart diff --git a/lib/src/nullable_extension.dart b/lib/src/nullable_extension.dart new file mode 100644 index 00000000..d3499384 --- /dev/null +++ b/lib/src/nullable_extension.dart @@ -0,0 +1,28 @@ +/// `fpdart` extension to chain methods on nullable values `T?` +extension FpdartOnNullable on T? { + /// Update the value using `f` if is not `null`, + /// otherwise just return `null`. + B? map(B Function(T t) f) { + final value = this; + return value == null ? null : f(value); + } + + /// {@template fpdart_nullable_extension_match} + /// Execute and return the result of `onNotNull` if the value is not `null`, + /// otherwise return the result of `onNotNull`. + /// {@endtemplate} + /// + /// If you want to return nullable values, use `matchNullable`. + B match(B Function() onNull, B Function(T t) onNotNull) { + final value = this; + return value == null ? onNull() : onNotNull(value); + } + + /// {@macro fpdart_nullable_extension_match} + /// + /// Same as `match`, but returns nullable values. + B? matchNullable(B? Function() onNull, B? Function(T t) onNotNull) { + final value = this; + return value == null ? onNull() : onNotNull(value); + } +} From 1a3ce3e6ad46f67a29ad3ad8a297d68f48725aba Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 19 Oct 2022 10:02:06 +0200 Subject: [PATCH 02/12] example of option and null safety --- example/src/option/option_nullable.dart | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 example/src/option/option_nullable.dart diff --git a/example/src/option/option_nullable.dart b/example/src/option/option_nullable.dart new file mode 100644 index 00000000..d08dd0d2 --- /dev/null +++ b/example/src/option/option_nullable.dart @@ -0,0 +1,31 @@ +// ignore_for_file: unchecked_use_of_nullable_value, undefined_getter +import 'package:fpdart/fpdart.dart'; +import 'package:glados/glados.dart'; + +void main(List args) { + int? nullableInt = 10; + if (nullableInt == null) { + print("Missing ‼️"); + } else { + print("Found $nullableInt 🎯"); + } + + /// 👆 Exactly the same as 👇 + + Option optionInt = Option.of(10); + optionInt.match(() { + print("Missing ‼️"); + }, (t) { + print("Found $nullableInt 🎯"); + }); + + /// Null safety and `Option` save you from `null` 🚀 + String? str = Random().nextBool() ? "string" : null; + Option optionStr = Random().nextBool() ? some("string") : none(); + + /// ⛔️ The property 'toLowerCase' can't be unconditionally accessed because the receiver can be 'null'. + str.toLowerCase; + + /// ⛔️ The getter 'toLowerCase' isn't defined for the type 'Option'. + optionStr.toLowerCase; +} From 6f92106e32ba0709081166f357d1102fa2c00e79 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 19 Oct 2022 19:15:27 +0200 Subject: [PATCH 03/12] example chain methods Option type --- example/src/option/option_nullable.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/example/src/option/option_nullable.dart b/example/src/option/option_nullable.dart index d08dd0d2..405353a5 100644 --- a/example/src/option/option_nullable.dart +++ b/example/src/option/option_nullable.dart @@ -2,6 +2,9 @@ import 'package:fpdart/fpdart.dart'; import 'package:glados/glados.dart'; +int doSomething(String str) => str.length + 10 * 2; +int doSomethingElse(int number) => number + 10 * 2; + void main(List args) { int? nullableInt = 10; if (nullableInt == null) { @@ -28,4 +31,15 @@ void main(List args) { /// ⛔️ The getter 'toLowerCase' isn't defined for the type 'Option'. optionStr.toLowerCase; + + /// Option has methods that makes it more powerful (chain methods) ⛓ + String? strNullable = Random().nextBool() ? "string" : null; + Option optionNullable = some("string"); + + Option optionIntNullable = + optionNullable.map(doSomething).alt(() => some(20)).map(doSomethingElse); + + int? intNullable = strNullable != null + ? doSomethingElse(doSomething(strNullable)) + : doSomethingElse(20); } From 74f8b56aa30c002a8c2e11d85f30d122b7bb85b1 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Thu, 20 Oct 2022 06:38:35 +0200 Subject: [PATCH 04/12] compare Option and null safety --- .../{ => nullable}/option_nullable.dart | 0 example/src/option/nullable/overview.dart | 46 +++++++++++++++++++ lib/fpdart.dart | 1 + 3 files changed, 47 insertions(+) rename example/src/option/{ => nullable}/option_nullable.dart (100%) create mode 100644 example/src/option/nullable/overview.dart diff --git a/example/src/option/option_nullable.dart b/example/src/option/nullable/option_nullable.dart similarity index 100% rename from example/src/option/option_nullable.dart rename to example/src/option/nullable/option_nullable.dart diff --git a/example/src/option/nullable/overview.dart b/example/src/option/nullable/overview.dart new file mode 100644 index 00000000..102d7c2d --- /dev/null +++ b/example/src/option/nullable/overview.dart @@ -0,0 +1,46 @@ +import 'dart:math'; + +import 'package:fpdart/fpdart.dart'; + +int? nullable() => Random().nextBool() ? 10 : null; + +String takesNullable(int? nullInt) => "$nullInt"; + +void main(List args) { + int noNull = 10; + int? canBeNull = nullable(); + + /// `bool` + final noNullIsEven = noNull.isEven; + + /// final canBeNullIsEven = canBeNull.isEven; ⛔️ + + /// `bool?` + final canBeNullIsEven = canBeNull?.isEven; + + /// ☑️ + takesNullable(canBeNull); + + /// ☑️ + takesNullable(noNull); + + /// ☑️ + noNull.abs(); + + /// ☑️ + canBeNull?.abs(); + + Option optionInt = Option.of(10); + int? nullInt = nullable(); + + nullInt?.abs(); + optionInt.map((t) => t.abs()); + + nullInt?.isEven; + optionInt.map((t) => t.isEven); + + takesNullable(nullInt); + + /// takesNullable(optionInt); ⛔️ + takesNullable(optionInt.toNullable()); +} diff --git a/lib/fpdart.dart b/lib/fpdart.dart index 12a259fb..5cf69518 100644 --- a/lib/fpdart.dart +++ b/lib/fpdart.dart @@ -8,6 +8,7 @@ export 'src/io_either.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'; From 51d9e54472f036426abc09831bd2f3067f0988b9 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 21 Oct 2022 06:39:17 +0200 Subject: [PATCH 05/12] toOption, toEither nullable types --- .../src/option/nullable/option_nullable.dart | 29 +++++++++++++++---- lib/src/nullable_extension.dart | 8 +++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/example/src/option/nullable/option_nullable.dart b/example/src/option/nullable/option_nullable.dart index 405353a5..5a2072af 100644 --- a/example/src/option/nullable/option_nullable.dart +++ b/example/src/option/nullable/option_nullable.dart @@ -36,10 +36,29 @@ void main(List args) { String? strNullable = Random().nextBool() ? "string" : null; Option optionNullable = some("string"); - Option optionIntNullable = - optionNullable.map(doSomething).alt(() => some(20)).map(doSomethingElse); + /// Declarative API: more readable and composable 🎉 + Option optionIntNullable = optionNullable + .map(doSomething) + .alt(() => some(20)) + .map(doSomethingElse) + .flatMap((t) => some(t / 2)); - int? intNullable = strNullable != null - ? doSomethingElse(doSomething(strNullable)) - : doSomethingElse(20); + /// Not really clear what is going on here 🤔 + double? intNullable = (strNullable != null + ? doSomethingElse(doSomething(strNullable)) + : doSomethingElse(20)) / + 2; + + if (optionNullable.isSome()) { + /// Still type `Option`, not `Some` 😐 + optionIntNullable; + } + + if (strNullable != null) { + /// This is now `String` 🤝 + strNullable; + } + + List? list = Random().nextBool() ? [1, 2, 3, 4] : null; + list.map((e) => /** What type is `e`? 😐 */ null); } diff --git a/lib/src/nullable_extension.dart b/lib/src/nullable_extension.dart index d3499384..ab9e226b 100644 --- a/lib/src/nullable_extension.dart +++ b/lib/src/nullable_extension.dart @@ -1,5 +1,13 @@ +import 'either.dart'; +import 'option.dart'; + /// `fpdart` extension to chain methods on nullable values `T?` extension FpdartOnNullable on T? { + Option toOption() => Option.fromNullable(this); + + Either toEither(L Function(T?) onNull) => + Either.fromNullable(this, onNull); + /// Update the value using `f` if is not `null`, /// otherwise just return `null`. B? map(B Function(T t) f) { From 2a8a726d62b502bea85a780d2f320361ccdd6e7f Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 22 Oct 2022 06:01:44 +0200 Subject: [PATCH 06/12] nullable `toOption`, `toEither` --- .../src/option/nullable/chain_nullable.dart | 11 +++++ lib/src/nullable_extension.dart | 34 ++++----------- test/src/nullable_extension_test.dart | 41 +++++++++++++++++++ 3 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 example/src/option/nullable/chain_nullable.dart create mode 100644 test/src/nullable_extension_test.dart diff --git a/example/src/option/nullable/chain_nullable.dart b/example/src/option/nullable/chain_nullable.dart new file mode 100644 index 00000000..606e3fc7 --- /dev/null +++ b/example/src/option/nullable/chain_nullable.dart @@ -0,0 +1,11 @@ +import 'dart:math'; + +int? nullable() => Random().nextBool() ? 10 : null; + +void main(List args) { + final value = 10; + // final either = value.toEither(); + + final nullableValue = nullable(); + // nullableValue.toEither(); +} diff --git a/lib/src/nullable_extension.dart b/lib/src/nullable_extension.dart index ab9e226b..4a35ffce 100644 --- a/lib/src/nullable_extension.dart +++ b/lib/src/nullable_extension.dart @@ -1,36 +1,16 @@ import 'either.dart'; import 'option.dart'; -/// `fpdart` extension to chain methods on nullable values `T?` +/// `fpdart` extension to work on nullable values `T?` extension FpdartOnNullable on T? { + /// Convert a nullable type `T?` to [Option]: + /// - [Some] if the value is **not** `null` + /// - [None] if the value is `null` Option toOption() => Option.fromNullable(this); + /// Convert a nullable type `T?` to [Either]: + /// - [Right] if the value is **not** `null` + /// - [Left] containing the result of `onNull` if the value is `null` Either toEither(L Function(T?) onNull) => Either.fromNullable(this, onNull); - - /// Update the value using `f` if is not `null`, - /// otherwise just return `null`. - B? map(B Function(T t) f) { - final value = this; - return value == null ? null : f(value); - } - - /// {@template fpdart_nullable_extension_match} - /// Execute and return the result of `onNotNull` if the value is not `null`, - /// otherwise return the result of `onNotNull`. - /// {@endtemplate} - /// - /// If you want to return nullable values, use `matchNullable`. - B match(B Function() onNull, B Function(T t) onNotNull) { - final value = this; - return value == null ? onNull() : onNotNull(value); - } - - /// {@macro fpdart_nullable_extension_match} - /// - /// Same as `match`, but returns nullable values. - B? matchNullable(B? Function() onNull, B? Function(T t) onNotNull) { - final value = this; - return value == null ? onNull() : onNotNull(value); - } } diff --git a/test/src/nullable_extension_test.dart b/test/src/nullable_extension_test.dart new file mode 100644 index 00000000..d0cbe649 --- /dev/null +++ b/test/src/nullable_extension_test.dart @@ -0,0 +1,41 @@ +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((l) => "$l"); + result.matchTestRight((t) { + expect(t, value); + }); + }); + + test('Left', () { + int? value = null; + final result = value.toEither((l) => "$l"); + result.matchTestLeft((l) { + expect(l, "null"); + }); + }); + }); + }); +} From 98d806e05e07eef30fb80d2833ad6c0c20a544de Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 22 Oct 2022 06:30:05 +0200 Subject: [PATCH 07/12] =?UTF-8?q?[=E2=9A=A0=EF=B8=8F]=20`fromNullable`=20T?= =?UTF-8?q?askEither,=20nullable=20extension=20Breaking=20change:=20Remove?= =?UTF-8?q?d=20parameter=20from=20Either=20`fromNullable`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/src/either/chain_either.dart | 2 +- .../src/option/nullable/chain_nullable.dart | 3 ++ lib/src/either.dart | 4 +- lib/src/nullable_extension.dart | 20 +++++++- lib/src/task_either.dart | 11 +++++ test/src/either_test.dart | 4 +- test/src/nullable_extension_test.dart | 46 +++++++++++++++++-- test/src/task_either_test.dart | 38 +++++++++++++++ 8 files changed, 119 insertions(+), 9 deletions(-) diff --git a/example/src/either/chain_either.dart b/example/src/either/chain_either.dart index 6f7b66c9..0dca6499 100644 --- a/example/src/either/chain_either.dart +++ b/example/src/either/chain_either.dart @@ -57,7 +57,7 @@ Future placeOrder() async { /// Same code using fpart and Functional programming Either.fromNullable( authRepository.currentUser?.uid, - (r) => 'Missing uid', + () => 'Missing uid', ).toTaskEither().flatMap( (uid) => TaskEither.tryCatch( () async => cartRepository.fetchCart(uid), diff --git a/example/src/option/nullable/chain_nullable.dart b/example/src/option/nullable/chain_nullable.dart index 606e3fc7..9990edc2 100644 --- a/example/src/option/nullable/chain_nullable.dart +++ b/example/src/option/nullable/chain_nullable.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:fpdart/fpdart.dart'; + int? nullable() => Random().nextBool() ? 10 : null; void main(List args) { @@ -7,5 +9,6 @@ void main(List args) { // final either = value.toEither(); final nullableValue = nullable(); + nullableValue.toTaskEitherAsync(Task.of(10)); // nullableValue.toEither(); } diff --git a/lib/src/either.dart b/lib/src/either.dart index 74efff3b..6b03eafd 100644 --- a/lib/src/either.dart +++ b/lib/src/either.dart @@ -387,8 +387,8 @@ abstract class Either extends HKT2<_EitherHKT, L, R> /// If `r` is `null`, then return the result of `onNull` in [Left]. /// Otherwise return `Right(r)`. - factory Either.fromNullable(R? r, L Function(R? r) onNull) => - r != null ? Either.of(r) : Either.left(onNull(r)); + factory Either.fromNullable(R? r, L Function() onNull) => + r != null ? Either.of(r) : Either.left(onNull()); /// Try to execute `run`. If no error occurs, then return [Right]. /// Otherwise return [Left] containing the result of `onError`. diff --git a/lib/src/nullable_extension.dart b/lib/src/nullable_extension.dart index 4a35ffce..17f68ec2 100644 --- a/lib/src/nullable_extension.dart +++ b/lib/src/nullable_extension.dart @@ -1,5 +1,7 @@ import 'either.dart'; import 'option.dart'; +import 'task.dart'; +import 'task_either.dart'; /// `fpdart` extension to work on nullable values `T?` extension FpdartOnNullable on T? { @@ -9,8 +11,24 @@ extension FpdartOnNullable on T? { 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` - Either toEither(L Function(T?) onNull) => + /// {@endtemplate} + Either toEither(L Function() onNull) => Either.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/lib/src/task_either.dart b/lib/src/task_either.dart index d8d460b8..1f14bc3b 100644 --- a/lib/src/task_either.dart +++ b/lib/src/task_either.dart @@ -183,6 +183,17 @@ class TaskEither extends HKT2<_TaskEitherHKT, L, R> factory TaskEither.fromTask(Task task) => TaskEither(() async => Right(await task.run())); + /// {@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(); + + /// {@macro fpdart_from_nullable_task_either} + factory TaskEither.fromNullableAsync(R? r, Task onNull) => TaskEither( + () async => r != null ? Either.of(r) : Either.left(await onNull.run())); + /// When calling `predicate` with `value` returns `true`, then running [TaskEither] returns `Right(value)`. /// Otherwise return `onFalse`. factory TaskEither.fromPredicate( diff --git a/test/src/either_test.dart b/test/src/either_test.dart index ec1379a7..58c6bf38 100644 --- a/test/src/either_test.dart +++ b/test/src/either_test.dart @@ -751,14 +751,14 @@ void main() { group('fromNullable', () { test('Right', () { - final either = Either.fromNullable(10, (r) => 'none'); + final either = Either.fromNullable(10, () => 'none'); either.match((_) { fail('should be right'); }, (r) => expect(r, 10)); }); test('Left', () { - final either = Either.fromNullable(null, (r) => 'none'); + final either = Either.fromNullable(null, () => 'none'); either.match((l) => expect(l, 'none'), (_) { fail('should be left'); }); diff --git a/test/src/nullable_extension_test.dart b/test/src/nullable_extension_test.dart index d0cbe649..98798457 100644 --- a/test/src/nullable_extension_test.dart +++ b/test/src/nullable_extension_test.dart @@ -23,7 +23,7 @@ void main() { group("toEither", () { test('Right', () { final value = 10; - final result = value.toEither((l) => "$l"); + final result = value.toEither(() => "Error"); result.matchTestRight((t) { expect(t, value); }); @@ -31,9 +31,49 @@ void main() { test('Left', () { int? value = null; - final result = value.toEither((l) => "$l"); + final result = value.toEither(() => "Error"); result.matchTestLeft((l) { - expect(l, "null"); + 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"); }); }); }); diff --git a/test/src/task_either_test.dart b/test/src/task_either_test.dart index fc50d208..0d4ea320 100644 --- a/test/src/task_either_test.dart +++ b/test/src/task_either_test.dart @@ -377,6 +377,44 @@ void main() { }); }); + 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( From 947208019e20687dc6a61a90373b160fc3510993 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 22 Oct 2022 06:38:29 +0200 Subject: [PATCH 08/12] `fromNullable`, `toTaskOption` TaskOption --- lib/src/nullable_extension.dart | 7 +++++++ lib/src/task_option.dart | 5 +++++ test/src/nullable_extension_test.dart | 18 ++++++++++++++++++ test/src/task_option_test.dart | 16 ++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/lib/src/nullable_extension.dart b/lib/src/nullable_extension.dart index 17f68ec2..27b7a7a4 100644 --- a/lib/src/nullable_extension.dart +++ b/lib/src/nullable_extension.dart @@ -2,12 +2,15 @@ import 'either.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]: @@ -18,6 +21,10 @@ extension FpdartOnNullable on T? { Either toEither(L Function() onNull) => Either.fromNullable(this, onNull); + /// Convert a nullable type `T?` to [TaskOption]: + /// {@macro fpdart_on_nullable_to_option} + TaskOption toTaskOption() => TaskOption.fromNullable(this); + /// Convert a nullable type `T?` to [TaskEither] using a **sync function**: /// {@macro fpdart_on_nullable_to_either} /// diff --git a/lib/src/task_option.dart b/lib/src/task_option.dart index 981cc59c..0789abde 100644 --- a/lib/src/task_option.dart +++ b/lib/src/task_option.dart @@ -150,6 +150,11 @@ class TaskOption extends HKT<_TaskOptionHKT, R> factory TaskOption.fromTask(Task task) => TaskOption(() async => Option.of(await task.run())); + /// If `r` is `null`, then return [None]. + /// Otherwise return `Right(r)`. + factory TaskOption.fromNullable(R? r) => + Option.fromNullable(r).toTaskOption(); + /// When calling `predicate` with `value` returns `true`, then running [TaskOption] returns `Some(value)`. /// Otherwise return [None]. factory TaskOption.fromPredicate(R value, bool Function(R a) predicate) => diff --git a/test/src/nullable_extension_test.dart b/test/src/nullable_extension_test.dart index 98798457..19b1d67e 100644 --- a/test/src/nullable_extension_test.dart +++ b/test/src/nullable_extension_test.dart @@ -38,6 +38,24 @@ void main() { }); }); + 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("toTaskEither", () { test('Right', () async { final value = 10; diff --git a/test/src/task_option_test.dart b/test/src/task_option_test.dart index dabcedbc..41d93cbb 100644 --- a/test/src/task_option_test.dart +++ b/test/src/task_option_test.dart @@ -210,6 +210,22 @@ void main() { }); }); + group('fromNullable', () { + test('Right', () async { + final task = TaskOption.fromNullable(10); + final result = await task.run(); + result.matchTestSome((r) { + expect(r, 10); + }); + }); + + test('Left', () async { + final task = TaskOption.fromNullable(null); + final result = await task.run(); + expect(result, isA>()); + }); + }); + group('fromPredicate', () { test('True', () async { final task = TaskOption.fromPredicate(20, (n) => n > 10); From fc3c22db81c24cc4e0a6fd23677f89bb78931c5f Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 22 Oct 2022 06:46:58 +0200 Subject: [PATCH 09/12] Convert Either to IOEither, also nullable --- lib/src/either.dart | 10 ++++++++++ lib/src/io_either.dart | 5 +++++ lib/src/nullable_extension.dart | 5 +++++ test/src/either_test.dart | 16 ++++++++++++++++ test/src/io_either_test.dart | 18 ++++++++++++++++++ test/src/nullable_extension_test.dart | 20 ++++++++++++++++++++ 6 files changed, 74 insertions(+) diff --git a/lib/src/either.dart b/lib/src/either.dart index 6b03eafd..de778360 100644 --- a/lib/src/either.dart +++ b/lib/src/either.dart @@ -1,4 +1,5 @@ import 'function.dart'; +import 'io_either.dart'; import 'option.dart'; import 'task_either.dart'; import 'tuple.dart'; @@ -195,6 +196,9 @@ abstract class Either extends HKT2<_EitherHKT, L, R> /// - If the [Either] is [Right], return a [Some] containing the value inside [Right] Option toOption(); + /// Convert this [Either] to a [IOEither]. + IOEither toIOEither(); + /// Convert this [Either] to a [TaskEither]. /// /// Used to convert a sync context ([Either]) to an async context ([TaskEither]). @@ -517,6 +521,9 @@ class Right extends Either { @override TaskEither toTaskEither() => TaskEither.of(_value); + + @override + IOEither toIOEither() => IOEither.of(_value); } class Left extends Either { @@ -600,4 +607,7 @@ class Left extends Either { @override TaskEither toTaskEither() => TaskEither.left(_value); + + @override + IOEither toIOEither() => IOEither.left(_value); } diff --git a/lib/src/io_either.dart b/lib/src/io_either.dart index 8f59204f..407c0caa 100644 --- a/lib/src/io_either.dart +++ b/lib/src/io_either.dart @@ -210,6 +210,11 @@ class IOEither extends HKT2<_IOEitherHKT, L, R> /// Build a [IOEither] that returns `either`. factory IOEither.fromEither(Either either) => IOEither(() => either); + /// If `r` is `null`, then return the result of `onNull` in [Left]. + /// Otherwise return `Right(r)`. + factory IOEither.fromNullable(R? r, L Function() onNull) => + Either.fromNullable(r, onNull).toIOEither(); + /// Converts a [Function] that may throw to a [Function] that never throws /// but returns a [Either] instead. /// diff --git a/lib/src/nullable_extension.dart b/lib/src/nullable_extension.dart index 27b7a7a4..093d5c59 100644 --- a/lib/src/nullable_extension.dart +++ b/lib/src/nullable_extension.dart @@ -1,4 +1,5 @@ import 'either.dart'; +import 'io_either.dart'; import 'option.dart'; import 'task.dart'; import 'task_either.dart'; @@ -25,6 +26,10 @@ extension FpdartOnNullable on T? { /// {@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} /// diff --git a/test/src/either_test.dart b/test/src/either_test.dart index 58c6bf38..174eebe0 100644 --- a/test/src/either_test.dart +++ b/test/src/either_test.dart @@ -436,6 +436,22 @@ void main() { }); }); + group('toIOEither', () { + test('Right', () { + final value = Either.of(10); + final ap = value.toIOEither(); + final result = ap.run(); + expect(result, value); + }); + + test('Left', () { + final value = Either.left('none'); + final ap = value.toIOEither(); + final result = ap.run(); + expect(result, value); + }); + }); + group('toTaskEither', () { test('Right', () async { final value = Either.of(10); diff --git a/test/src/io_either_test.dart b/test/src/io_either_test.dart index 548ff6c2..360a6700 100644 --- a/test/src/io_either_test.dart +++ b/test/src/io_either_test.dart @@ -370,6 +370,24 @@ void main() { }); }); + group('fromNullable', () { + test('Right', () { + final task = IOEither.fromNullable(10, () => "Error"); + final result = task.run(); + result.matchTestRight((r) { + expect(r, 10); + }); + }); + + test('Left', () { + final task = IOEither.fromNullable(null, () => "Error"); + final result = task.run(); + result.matchTestLeft((l) { + expect(l, "Error"); + }); + }); + }); + group('fromPredicate', () { test('True', () { final task = diff --git a/test/src/nullable_extension_test.dart b/test/src/nullable_extension_test.dart index 19b1d67e..cc69dc35 100644 --- a/test/src/nullable_extension_test.dart +++ b/test/src/nullable_extension_test.dart @@ -56,6 +56,26 @@ void main() { }); }); + 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; From fe92ef00a26ed2d1a0126f7aed6c6d4d88ef3ce0 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 22 Oct 2022 06:53:48 +0200 Subject: [PATCH 10/12] `toNullable` Either --- lib/src/either.dart | 12 ++++++++++++ test/src/either_test.dart | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/src/either.dart b/lib/src/either.dart index de778360..d7e2b391 100644 --- a/lib/src/either.dart +++ b/lib/src/either.dart @@ -206,6 +206,12 @@ abstract class Either extends HKT2<_EitherHKT, L, R> /// call an async ([Future]) function based on the value in [Either]. TaskEither toTaskEither(); + /// Convert [Either] to nullable `R?`. + /// + /// **Note**: this loses information about a possible [Left] value, + /// converting it to simply `null`. + R? toNullable(); + /// Return `true` when this [Either] is [Left]. bool isLeft(); @@ -524,6 +530,9 @@ class Right extends Either { @override IOEither toIOEither() => IOEither.of(_value); + + @override + R? toNullable() => _value; } class Left extends Either { @@ -610,4 +619,7 @@ class Left extends Either { @override IOEither toIOEither() => IOEither.left(_value); + + @override + R? toNullable() => null; } diff --git a/test/src/either_test.dart b/test/src/either_test.dart index 174eebe0..31b7a900 100644 --- a/test/src/either_test.dart +++ b/test/src/either_test.dart @@ -436,6 +436,20 @@ void main() { }); }); + group('toNullable', () { + test('Right', () { + final value = Either.of(10); + final ap = value.toNullable(); + expect(ap, 10); + }); + + test('Left', () { + final value = Either.left('none'); + final ap = value.toNullable(); + expect(ap, null); + }); + }); + group('toIOEither', () { test('Right', () { final value = Either.of(10); From 9207a3b5356431dff165cd080143fe59790f068c Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Sat, 22 Oct 2022 07:05:20 +0200 Subject: [PATCH 11/12] example nullable conversion --- .../src/option/nullable/chain_nullable.dart | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/example/src/option/nullable/chain_nullable.dart b/example/src/option/nullable/chain_nullable.dart index 9990edc2..88f7e0dd 100644 --- a/example/src/option/nullable/chain_nullable.dart +++ b/example/src/option/nullable/chain_nullable.dart @@ -5,10 +5,33 @@ import 'package:fpdart/fpdart.dart'; int? nullable() => Random().nextBool() ? 10 : null; void main(List args) { - final value = 10; - // final either = value.toEither(); + int? value1 = 10.toOption().map((t) => t + 10).toNullable(); + bool? value2 = value1?.isEven; + int? value3 = value2 + .toEither(() => "Error") + .flatMap((a) => a ? right(10) : left("None")) + .toNullable(); + Option value4 = (value3?.abs().round()).toOption().flatMap(Option.of); - final nullableValue = nullable(); - nullableValue.toTaskEitherAsync(Task.of(10)); - // nullableValue.toEither(); + 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); } From 758976efb44f964c02c7449fd2027cefdf26ab58 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Mon, 24 Oct 2022 05:52:12 +0200 Subject: [PATCH 12/12] updated CHANGELOG --- CHANGELOG.md | 39 +++++++++++++++++++ README.md | 1 + .../src/option/nullable/chain_nullable.dart | 6 +++ 3 files changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dac3a1fe..7c9334d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +# v0.4.0 - Soon +- Added extension methods to work with nullable types (`T?`) + - From `T?` to `fpdart`'s types + - `toOption` + - `toEither` + - `toTaskOption` + - `toIOEither` + - `toTaskEither` + - `toTaskEitherAsync` + - `fromNullable` (`Either`, `IOEither`, `TaskOption` `TaskEither`) + - `fromNullableAsync` (`TaskEither`) + - From `fpdart`'s types to `T?` + - `toNullable` (`Either`) +```dart +/// [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); +``` +- Added `toIOEither` to `Either` +- Removed parameter from `Either` `fromNullable` [⚠️ **BREAKING CHANGE**] +```dart +final either = Either.fromNullable(value, (r) => 'none'); + +/// 👆 Removed the value `(r)` (it was always null anyway 💁🏼‍♂️) 👇 + +final either = Either.fromNullable(value, () => 'none'); +``` +- Added article about [Option type and Null Safety in dart](https://www.sandromaglione.com/techblog/option_type_and_null_safety_dart) + # v0.3.0 - 11 October 2022 - Inverted `onSome` and `onNone` functions parameters in `match` method of `Option` [⚠️ **BREAKING CHANGE**] (*Read more on why* 👉 [#56](https://github.com/SandroMaglione/fpdart/pull/56)) ```dart diff --git a/README.md b/README.md index 7ae52343..f66b9124 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Check out also this series of articles about functional programming with `fpdart - [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) +- [Option type and Null Safety in dart](https://www.sandromaglione.com/techblog/option_type_and_null_safety_dart) ## 💻 Installation diff --git a/example/src/option/nullable/chain_nullable.dart b/example/src/option/nullable/chain_nullable.dart index 88f7e0dd..17229ad1 100644 --- a/example/src/option/nullable/chain_nullable.dart +++ b/example/src/option/nullable/chain_nullable.dart @@ -5,12 +5,18 @@ 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