From de54f0ebb8a01b52e5e1fd14115bba8d4e088b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20J=C3=A1nos=20Csillik?= Date: Wed, 28 May 2025 23:06:53 +0200 Subject: [PATCH 1/5] Add Flatten to monads --- Funcky.Test/Monads/EitherTest.Flatten.cs | 22 +++++++++++++++++++ Funcky.Test/Monads/LazyTest.Flatten.cs | 12 ++++++++++ Funcky.Test/Monads/OptionTest.Flatten.cs | 22 +++++++++++++++++++ Funcky.Test/Monads/ReaderTest.Flatten.cs | 12 ++++++++++ Funcky.Test/Monads/ResultTest.Flatten.cs | 22 +++++++++++++++++++ Funcky/Monads/Either/EitherExtensions.cs | 5 +++++ Funcky/Monads/Lazy/LazyExtensions.cs | 11 ++++++++++ Funcky/Monads/Option/OptionExtensions.cs | 4 ++++ Funcky/Monads/Reader/ReaderExtensions.cs | 9 ++++++++ .../Result/ResultExtensions.Traversable.cs | 2 +- Funcky/Monads/Result/ResultExtensions.cs | 8 +++++++ Funcky/PublicAPI.Shipped.txt | 5 +++++ 12 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 Funcky.Test/Monads/EitherTest.Flatten.cs create mode 100644 Funcky.Test/Monads/LazyTest.Flatten.cs create mode 100644 Funcky.Test/Monads/OptionTest.Flatten.cs create mode 100644 Funcky.Test/Monads/ReaderTest.Flatten.cs create mode 100644 Funcky.Test/Monads/ResultTest.Flatten.cs create mode 100644 Funcky/Monads/Lazy/LazyExtensions.cs create mode 100644 Funcky/Monads/Reader/ReaderExtensions.cs create mode 100644 Funcky/Monads/Result/ResultExtensions.cs diff --git a/Funcky.Test/Monads/EitherTest.Flatten.cs b/Funcky.Test/Monads/EitherTest.Flatten.cs new file mode 100644 index 00000000..69568666 --- /dev/null +++ b/Funcky.Test/Monads/EitherTest.Flatten.cs @@ -0,0 +1,22 @@ +namespace Funcky.Test.Monads; + +public sealed partial class EitherTest +{ + [Fact] + public void FlattenLeftIsLeft() + { + Assert.Equal("test", FunctionalAssert.Left(Either>.Left("test").Flatten())); + } + + [Fact] + public void FlattenRightLeftIsLeft() + { + Assert.Equal("test", FunctionalAssert.Left(Either>.Right(Either.Left("test")).Flatten())); + } + + [Fact] + public void FlattenRightRightIsRight() + { + Assert.Equal(4711, FunctionalAssert.Right(Either>.Right(Either.Right(4711)).Flatten())); + } +} diff --git a/Funcky.Test/Monads/LazyTest.Flatten.cs b/Funcky.Test/Monads/LazyTest.Flatten.cs new file mode 100644 index 00000000..4842c099 --- /dev/null +++ b/Funcky.Test/Monads/LazyTest.Flatten.cs @@ -0,0 +1,12 @@ +using FsCheck; +using Funcky.FsCheck; +using Funcky.Test.TestUtils; + +namespace Funcky.Test.Monads; + +public sealed partial class LazyTest +{ + [FunckyProperty] + public Property FlattenLazyLazyIsLazy(Lazy input) + => CheckAssert.Equal(Lazy.Return(input).Flatten(), input); +} diff --git a/Funcky.Test/Monads/OptionTest.Flatten.cs b/Funcky.Test/Monads/OptionTest.Flatten.cs new file mode 100644 index 00000000..2710959b --- /dev/null +++ b/Funcky.Test/Monads/OptionTest.Flatten.cs @@ -0,0 +1,22 @@ +namespace Funcky.Test.Monads; + +public sealed partial class OptionTest +{ + [Fact] + public void FlattenNoneIsNone() + { + FunctionalAssert.None(Option>.None.Flatten()); + } + + [Fact] + public void FlattenSomeNoneIsNone() + { + FunctionalAssert.None(Option.Some(Option.None).Flatten()); + } + + [Fact] + public void FlattenSomeSomeIsSome() + { + Assert.Equal(4711, FunctionalAssert.Some(Option.Some(Option.Some(4711)).Flatten())); + } +} diff --git a/Funcky.Test/Monads/ReaderTest.Flatten.cs b/Funcky.Test/Monads/ReaderTest.Flatten.cs new file mode 100644 index 00000000..2f8f7fa1 --- /dev/null +++ b/Funcky.Test/Monads/ReaderTest.Flatten.cs @@ -0,0 +1,12 @@ +using FsCheck; +using Funcky.FsCheck; +using Funcky.Test.TestUtils; + +namespace Funcky.Test.Monads; + +public sealed partial class ReaderTest +{ + [FunckyProperty] + public Property FlattenReaderReaderIsReader(string environment, Reader input) + => CheckAssert.Equal(input, Reader.Return(input).Flatten(), environment); +} diff --git a/Funcky.Test/Monads/ResultTest.Flatten.cs b/Funcky.Test/Monads/ResultTest.Flatten.cs new file mode 100644 index 00000000..7a4251c2 --- /dev/null +++ b/Funcky.Test/Monads/ResultTest.Flatten.cs @@ -0,0 +1,22 @@ +namespace Funcky.Test.Monads; + +public sealed partial class ResultTest +{ + [Fact] + public void FlattenErrorIsError() + { + FunctionalAssert.Error(Result>.Error(new Exception()).Flatten()); + } + + [Fact] + public void FlattenSomeNoneIsNone() + { + FunctionalAssert.Error(Result.Ok(Result.Error(new Exception())).Flatten()); + } + + [Fact] + public void FlattenSomeSomeIsSome() + { + Assert.Equal(4711, FunctionalAssert.Ok(Result.Ok(Result.Ok(4711)).Flatten())); + } +} diff --git a/Funcky/Monads/Either/EitherExtensions.cs b/Funcky/Monads/Either/EitherExtensions.cs index 126c2e11..55b13ad2 100644 --- a/Funcky/Monads/Either/EitherExtensions.cs +++ b/Funcky/Monads/Either/EitherExtensions.cs @@ -2,6 +2,11 @@ namespace Funcky.Monads; public static partial class EitherExtensions { + public static Either Flatten(this Either> either) + where TLeft : notnull + where TRight : notnull + => either.SelectMany(Identity); + /// Returns the left value or if the is a right value. [Pure] public static Option LeftOrNone(this Either either) diff --git a/Funcky/Monads/Lazy/LazyExtensions.cs b/Funcky/Monads/Lazy/LazyExtensions.cs new file mode 100644 index 00000000..8c64b855 --- /dev/null +++ b/Funcky/Monads/Lazy/LazyExtensions.cs @@ -0,0 +1,11 @@ +using System.Diagnostics.CodeAnalysis; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace Funcky.Monads; + +public static partial class LazyExtensions +{ + public static Lazy Flatten<[DynamicallyAccessedMembers(PublicParameterlessConstructor)] T>(this Lazy> lazy) + where T : notnull + => lazy.SelectMany(Identity); +} diff --git a/Funcky/Monads/Option/OptionExtensions.cs b/Funcky/Monads/Option/OptionExtensions.cs index 0e516c64..d6f3bc91 100644 --- a/Funcky/Monads/Option/OptionExtensions.cs +++ b/Funcky/Monads/Option/OptionExtensions.cs @@ -4,6 +4,10 @@ namespace Funcky.Monads; public static partial class OptionExtensions { + public static Option Flatten(this Option> option) + where T : notnull + => option.SelectMany(Identity); + public static Either ToEither(this Option option, TLeft left) where TLeft : notnull where TRight : notnull diff --git a/Funcky/Monads/Reader/ReaderExtensions.cs b/Funcky/Monads/Reader/ReaderExtensions.cs new file mode 100644 index 00000000..2b3685a0 --- /dev/null +++ b/Funcky/Monads/Reader/ReaderExtensions.cs @@ -0,0 +1,9 @@ +namespace Funcky.Monads; + +public static partial class ReaderExtensions +{ + public static Reader Flatten(this Reader> reader) + where TEnvironment : notnull + where TItem : notnull + => reader.SelectMany(Identity); +} diff --git a/Funcky/Monads/Result/ResultExtensions.Traversable.cs b/Funcky/Monads/Result/ResultExtensions.Traversable.cs index 8c21d6ff..70eed4b4 100644 --- a/Funcky/Monads/Result/ResultExtensions.Traversable.cs +++ b/Funcky/Monads/Result/ResultExtensions.Traversable.cs @@ -3,7 +3,7 @@ namespace Funcky.Monads; -public static class ResultExtensions +public static partial class ResultExtensions { [Pure] public static Either> Traverse( diff --git a/Funcky/Monads/Result/ResultExtensions.cs b/Funcky/Monads/Result/ResultExtensions.cs new file mode 100644 index 00000000..b929c30c --- /dev/null +++ b/Funcky/Monads/Result/ResultExtensions.cs @@ -0,0 +1,8 @@ +namespace Funcky.Monads; + +public static partial class ResultExtensions +{ + public static Result Flatten(this Result> result) + where T : notnull + => result.SelectMany(Identity); +} diff --git a/Funcky/PublicAPI.Shipped.txt b/Funcky/PublicAPI.Shipped.txt index 2650399f..1704ef80 100644 --- a/Funcky/PublicAPI.Shipped.txt +++ b/Funcky/PublicAPI.Shipped.txt @@ -758,11 +758,13 @@ static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Mon static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Monads.Either either, System.Func!>! selector) -> Funcky.Monads.Reader>! static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Monads.Either either, System.Func>! selector) -> Funcky.Monads.Option> static Funcky.Monads.EitherExtensions.Traverse(this Funcky.Monads.Either either, System.Func>! selector) -> Funcky.Monads.Result> +static Funcky.Monads.EitherExtensions.Flatten(this Funcky.Monads.Either> either) -> Funcky.Monads.Either static Funcky.Monads.Lazy.FromFunc(System.Func! valueFactory) -> System.Lazy! static Funcky.Monads.Lazy.Return(T value) -> System.Lazy! static Funcky.Monads.LazyExtensions.Select(this System.Lazy! lazy, System.Func! selector) -> System.Lazy! static Funcky.Monads.LazyExtensions.SelectMany(this System.Lazy! lazy, System.Func!>! selector, System.Func! resultSelector) -> System.Lazy! static Funcky.Monads.LazyExtensions.SelectMany(this System.Lazy! lazy, System.Func!>! selector) -> System.Lazy! +static Funcky.Monads.LazyExtensions.Flatten(this System.Lazy!>! lazy) -> System.Lazy! static Funcky.Monads.Option.FromBoolean(bool boolean) -> Funcky.Monads.Option static Funcky.Monads.Option.FromBoolean(bool boolean, System.Func! selector) -> Funcky.Monads.Option static Funcky.Monads.Option.FromBoolean(bool boolean, TItem item) -> Funcky.Monads.Option @@ -797,12 +799,14 @@ static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Opti static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func!>! selector) -> Funcky.Monads.Reader>! static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func>! selector) -> Funcky.Monads.Either> static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func>! selector) -> Funcky.Monads.Result> +static Funcky.Monads.OptionExtensions.Flatten(this Funcky.Monads.Option> option) -> Funcky.Monads.Option static Funcky.Monads.Reader.FromAction(System.Action! action) -> Funcky.Monads.Reader! static Funcky.Monads.Reader.FromFunc(System.Func! function) -> Funcky.Monads.Reader! static Funcky.Monads.Reader.Return(TResult value) -> Funcky.Monads.Reader! static Funcky.Monads.ReaderExtensions.Select(this Funcky.Monads.Reader! source, System.Func! selector) -> Funcky.Monads.Reader! static Funcky.Monads.ReaderExtensions.SelectMany(this Funcky.Monads.Reader! source, System.Func!>! selector, System.Func! resultSelector) -> Funcky.Monads.Reader! static Funcky.Monads.ReaderExtensions.SelectMany(this Funcky.Monads.Reader! source, System.Func!>! selector) -> Funcky.Monads.Reader! +static Funcky.Monads.ReaderExtensions.Flatten(this Funcky.Monads.Reader!>! reader) -> Funcky.Monads.Reader! static Funcky.Monads.Result.Ok(TValidResult result) -> Funcky.Monads.Result static Funcky.Monads.Result.Return(TValidResult result) -> Funcky.Monads.Result static Funcky.Monads.Result.Error(System.Exception! exception) -> Funcky.Monads.Result @@ -819,6 +823,7 @@ static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Mona static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Monads.Result result, System.Func!>! selector) -> Funcky.Monads.Reader>! static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Monads.Result result, System.Func>! selector) -> Funcky.Monads.Option> static Funcky.Monads.ResultExtensions.Traverse(this Funcky.Monads.Result result, System.Func>! selector) -> Funcky.Monads.Either> +static Funcky.Monads.ResultExtensions.Flatten(this Funcky.Monads.Result> result) -> Funcky.Monads.Result static Funcky.Sequence.Concat(params System.Collections.Generic.IEnumerable![]! sources) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.Concat(System.Collections.Generic.IEnumerable!>! sources) -> System.Collections.Generic.IEnumerable! static Funcky.Sequence.Cycle(TResult element) -> System.Collections.Generic.IEnumerable! From a9b8f248fe0d64fd65d9d86a66c1040a95f3d1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20J=C3=A1nos=20Csillik?= Date: Wed, 28 May 2025 23:34:32 +0200 Subject: [PATCH 2/5] Simplify asserts --- Funcky.Test/Monads/EitherTest.Flatten.cs | 2 +- Funcky.Test/Monads/OptionTest.Flatten.cs | 2 +- Funcky.Test/Monads/ResultTest.Flatten.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Funcky.Test/Monads/EitherTest.Flatten.cs b/Funcky.Test/Monads/EitherTest.Flatten.cs index 69568666..f3ac9e6e 100644 --- a/Funcky.Test/Monads/EitherTest.Flatten.cs +++ b/Funcky.Test/Monads/EitherTest.Flatten.cs @@ -17,6 +17,6 @@ public void FlattenRightLeftIsLeft() [Fact] public void FlattenRightRightIsRight() { - Assert.Equal(4711, FunctionalAssert.Right(Either>.Right(Either.Right(4711)).Flatten())); + FunctionalAssert.Right(4711, Either>.Right(Either.Right(4711)).Flatten()); } } diff --git a/Funcky.Test/Monads/OptionTest.Flatten.cs b/Funcky.Test/Monads/OptionTest.Flatten.cs index 2710959b..d2d3a2be 100644 --- a/Funcky.Test/Monads/OptionTest.Flatten.cs +++ b/Funcky.Test/Monads/OptionTest.Flatten.cs @@ -17,6 +17,6 @@ public void FlattenSomeNoneIsNone() [Fact] public void FlattenSomeSomeIsSome() { - Assert.Equal(4711, FunctionalAssert.Some(Option.Some(Option.Some(4711)).Flatten())); + FunctionalAssert.Some(4711, Option.Some(Option.Some(4711)).Flatten()); } } diff --git a/Funcky.Test/Monads/ResultTest.Flatten.cs b/Funcky.Test/Monads/ResultTest.Flatten.cs index 7a4251c2..0946962e 100644 --- a/Funcky.Test/Monads/ResultTest.Flatten.cs +++ b/Funcky.Test/Monads/ResultTest.Flatten.cs @@ -17,6 +17,6 @@ public void FlattenSomeNoneIsNone() [Fact] public void FlattenSomeSomeIsSome() { - Assert.Equal(4711, FunctionalAssert.Ok(Result.Ok(Result.Ok(4711)).Flatten())); + FunctionalAssert.Ok(4711, Result.Ok(Result.Ok(4711)).Flatten()); } } From 93d2d12321a330cb41399ebbb70180fedf5b164f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20J=C3=A1nos=20Csillik?= Date: Wed, 28 May 2025 23:46:32 +0200 Subject: [PATCH 3/5] Fix generic constrtains --- Funcky/Monads/Lazy/LazyExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Funcky/Monads/Lazy/LazyExtensions.cs b/Funcky/Monads/Lazy/LazyExtensions.cs index 8c64b855..ce7ca0c9 100644 --- a/Funcky/Monads/Lazy/LazyExtensions.cs +++ b/Funcky/Monads/Lazy/LazyExtensions.cs @@ -6,6 +6,5 @@ namespace Funcky.Monads; public static partial class LazyExtensions { public static Lazy Flatten<[DynamicallyAccessedMembers(PublicParameterlessConstructor)] T>(this Lazy> lazy) - where T : notnull => lazy.SelectMany(Identity); } From d37efbf3189b0143cefb01d34b1189d772232d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20J=C3=A1nos=20Csillik?= Date: Thu, 29 May 2025 15:31:13 +0200 Subject: [PATCH 4/5] Add Flatten to IEnumerable --- Funcky/Monads/IEnumerable/IEnumerableExtensions.cs | 8 ++++++++ Funcky/PublicAPI.Shipped.txt | 2 ++ 2 files changed, 10 insertions(+) create mode 100644 Funcky/Monads/IEnumerable/IEnumerableExtensions.cs diff --git a/Funcky/Monads/IEnumerable/IEnumerableExtensions.cs b/Funcky/Monads/IEnumerable/IEnumerableExtensions.cs new file mode 100644 index 00000000..213e2f6b --- /dev/null +++ b/Funcky/Monads/IEnumerable/IEnumerableExtensions.cs @@ -0,0 +1,8 @@ +namespace Funcky.Monads; + +public static partial class IEnumerableExtensions +{ + public static IEnumerable Flatten(this IEnumerable> enumerable) + where T : notnull + => enumerable.SelectMany(Identity); +} diff --git a/Funcky/PublicAPI.Shipped.txt b/Funcky/PublicAPI.Shipped.txt index 1704ef80..46bd6f3f 100644 --- a/Funcky/PublicAPI.Shipped.txt +++ b/Funcky/PublicAPI.Shipped.txt @@ -123,6 +123,7 @@ Funcky.Monads.OptionEqualityComparer Funcky.Monads.OptionExtensions Funcky.Monads.OptionJsonConverter Funcky.Monads.OptionJsonConverter.OptionJsonConverter() -> void +Funcky.Monads.IEnumerableExtensions Funcky.Monads.Reader Funcky.Monads.Reader Funcky.Monads.ReaderExtensions @@ -800,6 +801,7 @@ static Funcky.Monads.OptionExtensions.Traverse(thi static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func>! selector) -> Funcky.Monads.Either> static Funcky.Monads.OptionExtensions.Traverse(this Funcky.Monads.Option option, System.Func>! selector) -> Funcky.Monads.Result> static Funcky.Monads.OptionExtensions.Flatten(this Funcky.Monads.Option> option) -> Funcky.Monads.Option +static Funcky.Monads.IEnumerableExtensions.Flatten(this System.Collections.Generic.IEnumerable!>! enumerable) -> System.Collections.Generic.IEnumerable! static Funcky.Monads.Reader.FromAction(System.Action! action) -> Funcky.Monads.Reader! static Funcky.Monads.Reader.FromFunc(System.Func! function) -> Funcky.Monads.Reader! static Funcky.Monads.Reader.Return(TResult value) -> Funcky.Monads.Reader! From cae6dbf2872bad9f7445f5ed9055aff1e746efa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20J=C3=A1nos=20Csillik?= Date: Thu, 29 May 2025 16:32:14 +0200 Subject: [PATCH 5/5] Fix Lazy> constrains --- Funcky/Monads/Lazy/LazyExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Funcky/Monads/Lazy/LazyExtensions.cs b/Funcky/Monads/Lazy/LazyExtensions.cs index ce7ca0c9..c00f9aa5 100644 --- a/Funcky/Monads/Lazy/LazyExtensions.cs +++ b/Funcky/Monads/Lazy/LazyExtensions.cs @@ -6,5 +6,6 @@ namespace Funcky.Monads; public static partial class LazyExtensions { public static Lazy Flatten<[DynamicallyAccessedMembers(PublicParameterlessConstructor)] T>(this Lazy> lazy) + where T : new() => lazy.SelectMany(Identity); }