From 6b9b7da2bc2d77e5588cc281dc651457adcadb20 Mon Sep 17 00:00:00 2001 From: Thomas Bruderer Date: Mon, 8 Jun 2020 11:10:15 +0200 Subject: [PATCH 01/10] Implements #31 FirstOrNone, LastOrNone and SingleOrNone --- Funcky/Extensions/EnumerableExtensions.cs | 72 +++++++++++++++++++++++ changelog.md | 3 + 2 files changed, 75 insertions(+) diff --git a/Funcky/Extensions/EnumerableExtensions.cs b/Funcky/Extensions/EnumerableExtensions.cs index 2f662fec..a098c255 100644 --- a/Funcky/Extensions/EnumerableExtensions.cs +++ b/Funcky/Extensions/EnumerableExtensions.cs @@ -74,5 +74,77 @@ public static void Each(this IEnumerable elements, Action action) action(element); } } + + /// + /// Returns the first element of a sequence as an option, or a None value if the sequence contains no elements. + /// + /// the inner type of the enumerable. + public static Option FirstOrNone(this IEnumerable source) + where TSource : notnull + { + return source.FirstOrDefault() is { } value + ? Option.Some(value) + : Option.None(); + } + + /// + /// Returns the first element of the sequence as an option that satisfies a condition or a None value if no such element is found. + /// + /// the inner type of the enumerable. + public static Option FirstOrNone(this IEnumerable source, Func predicate) + where TSource : notnull + { + return source.FirstOrDefault(predicate) is { } value + ? Option.Some(value) + : Option.None(); + } + + /// + /// Returns the last element of a sequence as an option, or a None value if the sequence contains no elements. + /// + /// the inner type of the enumerable. + public static Option LastOrNone(this IEnumerable source) + where TSource : notnull + { + return source.LastOrDefault() is { } value + ? Option.Some(value) + : Option.None(); + } + + /// + /// Returns the last element of a sequence that satisfies a condition as an option or a None value if no such element is found. + /// + /// the inner type of the enumerable. + public static Option LastOrNone(this IEnumerable source, Func predicate) + where TSource : notnull + { + return source.LastOrDefault(predicate) is { } value + ? Option.Some(value) + : Option.None(); + } + + /// + /// Returns the only element of a sequence as an option, or a None value if the sequence is empty; this method throws an exception if there is more than one element in the sequence. + /// + /// the inner type of the enumerable. + public static Option SingleOrNone(this IEnumerable source) + where TSource : notnull + { + return source.SingleOrDefault() is { } value + ? Option.Some(value) + : Option.None(); + } + + /// + /// Returns the only element of a sequence that satisfies a specified condition as an option or a None value if no such element exists; this method throws an exception if more than one element satisfies the condition. + /// + /// the inner type of the enumerable. + public static Option SingleOrNone(this IEnumerable source, Func predicate) + where TSource : notnull + { + return source.SingleOrDefault(predicate) is { } value + ? Option.Some(value) + : Option.None(); + } } } diff --git a/changelog.md b/changelog.md index d5f86ea3..806d9b59 100644 --- a/changelog.md +++ b/changelog.md @@ -9,3 +9,6 @@ * Add nullability annotations to everything except for `Monads.Reader`. * Add a function for creating an `Option` from a nullable value: `Option.From`. * `Either.Match` now throws when called on an `Either` value created using `default(Either)`. +* Add True and False functions to public API +* Match of Result Monad accepts actions +* Add FirstOrNone, LastorNone and SingleOrNone extension functions From 2f3de4133a0b74a002b84318f5cb4ad57adc79f4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 8 Jun 2020 11:52:04 +0200 Subject: [PATCH 02/10] Update changelog.md use backticks for Code Co-authored-by: Ruben Schmidmeister --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 806d9b59..4d86434f 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,6 @@ * Add nullability annotations to everything except for `Monads.Reader`. * Add a function for creating an `Option` from a nullable value: `Option.From`. * `Either.Match` now throws when called on an `Either` value created using `default(Either)`. -* Add True and False functions to public API +* Add `True` and `False` functions to public API * Match of Result Monad accepts actions * Add FirstOrNone, LastorNone and SingleOrNone extension functions From f36c5dd854e24713edba0d68ede56076a7feb202 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 8 Jun 2020 11:52:19 +0200 Subject: [PATCH 03/10] Update changelog.md use backticks for code Co-authored-by: Ruben Schmidmeister --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 4d86434f..d74e0d49 100644 --- a/changelog.md +++ b/changelog.md @@ -11,4 +11,4 @@ * `Either.Match` now throws when called on an `Either` value created using `default(Either)`. * Add `True` and `False` functions to public API * Match of Result Monad accepts actions -* Add FirstOrNone, LastorNone and SingleOrNone extension functions +* Add `FirstOrNone`, `LastOrNone` and `SingleOrNone` extension functions From efa00785406acf7bf0db99f3a9d6c5b23e700096 Mon Sep 17 00:00:00 2001 From: Thomas Bruderer Date: Mon, 8 Jun 2020 12:09:26 +0200 Subject: [PATCH 04/10] Fixed implementation for value types * added tests * use expression body syntax --- Funcky.Test/EnumerableExtensionTest.cs | 57 +++++++++++++++++++- Funcky/Extensions/EnumerableExtensions.cs | 63 ++++++++++------------- changelog.md | 2 +- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/Funcky.Test/EnumerableExtensionTest.cs b/Funcky.Test/EnumerableExtensionTest.cs index 5ca71db8..35b7ce83 100644 --- a/Funcky.Test/EnumerableExtensionTest.cs +++ b/Funcky.Test/EnumerableExtensionTest.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Funcky.Extensions; using Funcky.Monads; using Xunit; +using static Funcky.Functional; + namespace Funcky.Test { public class EnumerableExtensionTest @@ -77,6 +80,33 @@ public void WhereSelectFiltersEmptyValues() Assert.Equal(expectedResult, result); } + [Theory] + [MemberData(nameof(ValueReferenceEnumerables))] + public void GivenAnValueEnumerableFirstLastOrNoneGivesTheCorrectOption(List valueEnumerable, List referenceEnumerable) + { + Assert.Equal(ExpectedOptionValue(valueEnumerable), valueEnumerable.FirstOrNone().Match(false, True)); + Assert.Equal(ExpectedOptionValue(valueEnumerable), referenceEnumerable.FirstOrNone().Match(false, True)); + + Assert.Equal(ExpectedOptionValue(valueEnumerable), valueEnumerable.LastOrNone().Match(false, True)); + Assert.Equal(ExpectedOptionValue(valueEnumerable), referenceEnumerable.LastOrNone().Match(false, True)); + } + + [Theory] + [MemberData(nameof(ValueReferenceEnumerables))] + public void GivenAnEnumerableSingleOrNoneGivesTheCorrectOption(List valueEnumerable, List referenceEnumerable) + { + ExpectedSingleOrNoneBehaviour(valueEnumerable, () => valueEnumerable.SingleOrNone().Match(false, True)); + ExpectedSingleOrNoneBehaviour(valueEnumerable, () => referenceEnumerable.SingleOrNone().Match(false, True)); + } + + public static TheoryData, List> ValueReferenceEnumerables() + => new TheoryData, List> + { + { new List(), new List() }, + { new List { 1 }, new List { "a" } }, + { new List { 1, 2, 3 }, new List { "a", "b", "c" } }, + }; + private static Option SquareEvenNumbers(int number) => IsEven(number) ? Option.Some(number * number) : Option.None(); @@ -97,5 +127,30 @@ private void AcceptUnits(IEnumerable units) Assert.Equal(default, unit); } } + + private bool ExpectedOptionValue(List valueEnumerable) + { + return valueEnumerable.Count switch + { + 0 => false, + _ => true, + }; + } + + private void ExpectedSingleOrNoneBehaviour(List list, Func singleOrNone) + { + switch (list.Count) + { + case 0: + Assert.False(singleOrNone()); + break; + case 1: + Assert.True(singleOrNone()); + break; + default: + Assert.Throws(() => singleOrNone()); + break; + } + } } } diff --git a/Funcky/Extensions/EnumerableExtensions.cs b/Funcky/Extensions/EnumerableExtensions.cs index a098c255..91619144 100644 --- a/Funcky/Extensions/EnumerableExtensions.cs +++ b/Funcky/Extensions/EnumerableExtensions.cs @@ -80,71 +80,62 @@ public static void Each(this IEnumerable elements, Action action) /// /// the inner type of the enumerable. public static Option FirstOrNone(this IEnumerable source) - where TSource : notnull - { - return source.FirstOrDefault() is { } value - ? Option.Some(value) - : Option.None(); - } + where TSource : notnull => + source + .Select(Option.Some) + .FirstOrDefault(); /// /// Returns the first element of the sequence as an option that satisfies a condition or a None value if no such element is found. /// /// the inner type of the enumerable. public static Option FirstOrNone(this IEnumerable source, Func predicate) - where TSource : notnull - { - return source.FirstOrDefault(predicate) is { } value - ? Option.Some(value) - : Option.None(); - } + where TSource : notnull => + source + .Where(predicate) + .Select(Option.Some) + .FirstOrDefault(); /// /// Returns the last element of a sequence as an option, or a None value if the sequence contains no elements. /// /// the inner type of the enumerable. public static Option LastOrNone(this IEnumerable source) - where TSource : notnull - { - return source.LastOrDefault() is { } value - ? Option.Some(value) - : Option.None(); - } + where TSource : notnull => + source + .Select(Option.Some) + .LastOrDefault(); /// /// Returns the last element of a sequence that satisfies a condition as an option or a None value if no such element is found. /// /// the inner type of the enumerable. public static Option LastOrNone(this IEnumerable source, Func predicate) - where TSource : notnull - { - return source.LastOrDefault(predicate) is { } value - ? Option.Some(value) - : Option.None(); - } + where TSource : notnull => + source + .Where(predicate) + .Select(Option.Some) + .LastOrDefault(); /// /// Returns the only element of a sequence as an option, or a None value if the sequence is empty; this method throws an exception if there is more than one element in the sequence. /// /// the inner type of the enumerable. public static Option SingleOrNone(this IEnumerable source) - where TSource : notnull - { - return source.SingleOrDefault() is { } value - ? Option.Some(value) - : Option.None(); - } + where TSource : notnull => + source + .Select(Option.Some) + .SingleOrDefault(); /// /// Returns the only element of a sequence that satisfies a specified condition as an option or a None value if no such element exists; this method throws an exception if more than one element satisfies the condition. /// /// the inner type of the enumerable. public static Option SingleOrNone(this IEnumerable source, Func predicate) - where TSource : notnull - { - return source.SingleOrDefault(predicate) is { } value - ? Option.Some(value) - : Option.None(); - } + where TSource : notnull => + source + .Where(predicate) + .Select(Option.Some) + .SingleOrDefault(); } } diff --git a/changelog.md b/changelog.md index d74e0d49..173a4beb 100644 --- a/changelog.md +++ b/changelog.md @@ -10,5 +10,5 @@ * Add a function for creating an `Option` from a nullable value: `Option.From`. * `Either.Match` now throws when called on an `Either` value created using `default(Either)`. * Add `True` and `False` functions to public API -* Match of Result Monad accepts actions +* Match of `Result` Monad accepts actions * Add `FirstOrNone`, `LastOrNone` and `SingleOrNone` extension functions From 02377ad1a3cd53b8b6ae89485acaa75beba8ed08 Mon Sep 17 00:00:00 2001 From: Thomas Bruderer Date: Mon, 8 Jun 2020 12:23:02 +0200 Subject: [PATCH 05/10] Use cref for Option and Option.Some --- Funcky/Extensions/EnumerableExtensions.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Funcky/Extensions/EnumerableExtensions.cs b/Funcky/Extensions/EnumerableExtensions.cs index 91619144..965eb673 100644 --- a/Funcky/Extensions/EnumerableExtensions.cs +++ b/Funcky/Extensions/EnumerableExtensions.cs @@ -76,7 +76,7 @@ public static void Each(this IEnumerable elements, Action action) } /// - /// Returns the first element of a sequence as an option, or a None value if the sequence contains no elements. + /// Returns the first element of a sequence as an , or a value if the sequence contains no elements. /// /// the inner type of the enumerable. public static Option FirstOrNone(this IEnumerable source) @@ -86,7 +86,7 @@ public static Option FirstOrNone(this IEnumerable sou .FirstOrDefault(); /// - /// Returns the first element of the sequence as an option that satisfies a condition or a None value if no such element is found. + /// Returns the first element of the sequence as an that satisfies a condition or a value if no such element is found. /// /// the inner type of the enumerable. public static Option FirstOrNone(this IEnumerable source, Func predicate) @@ -97,7 +97,7 @@ public static Option FirstOrNone(this IEnumerable sou .FirstOrDefault(); /// - /// Returns the last element of a sequence as an option, or a None value if the sequence contains no elements. + /// Returns the last element of a sequence as an , or a value if the sequence contains no elements. /// /// the inner type of the enumerable. public static Option LastOrNone(this IEnumerable source) @@ -107,7 +107,7 @@ public static Option LastOrNone(this IEnumerable sour .LastOrDefault(); /// - /// Returns the last element of a sequence that satisfies a condition as an option or a None value if no such element is found. + /// Returns the last element of a sequence that satisfies a condition as an or a value if no such element is found. /// /// the inner type of the enumerable. public static Option LastOrNone(this IEnumerable source, Func predicate) @@ -118,7 +118,7 @@ public static Option LastOrNone(this IEnumerable sour .LastOrDefault(); /// - /// Returns the only element of a sequence as an option, or a None value if the sequence is empty; this method throws an exception if there is more than one element in the sequence. + /// Returns the only element of a sequence as an , or a value if the sequence is empty; this method throws an exception if there is more than one element in the sequence. /// /// the inner type of the enumerable. public static Option SingleOrNone(this IEnumerable source) @@ -128,7 +128,7 @@ public static Option SingleOrNone(this IEnumerable so .SingleOrDefault(); /// - /// Returns the only element of a sequence that satisfies a specified condition as an option or a None value if no such element exists; this method throws an exception if more than one element satisfies the condition. + /// Returns the only element of a sequence that satisfies a specified condition as an or a value if no such element exists; this method throws an exception if more than one element satisfies the condition. /// /// the inner type of the enumerable. public static Option SingleOrNone(this IEnumerable source, Func predicate) From 303b8d6b685f0f624a09aa235f27f1a790e50506 Mon Sep 17 00:00:00 2001 From: Thomas Bruderer Date: Mon, 8 Jun 2020 12:26:37 +0200 Subject: [PATCH 06/10] To expression body --- Funcky.Test/EnumerableExtensionTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Funcky.Test/EnumerableExtensionTest.cs b/Funcky.Test/EnumerableExtensionTest.cs index 35b7ce83..ac5e3c22 100644 --- a/Funcky.Test/EnumerableExtensionTest.cs +++ b/Funcky.Test/EnumerableExtensionTest.cs @@ -128,14 +128,12 @@ private void AcceptUnits(IEnumerable units) } } - private bool ExpectedOptionValue(List valueEnumerable) - { - return valueEnumerable.Count switch + private bool ExpectedOptionValue(List valueEnumerable) => + valueEnumerable.Count switch { 0 => false, _ => true, }; - } private void ExpectedSingleOrNoneBehaviour(List list, Func singleOrNone) { From a7e9f87c0cb845a1780a2186c0e091772cfe3a6f Mon Sep 17 00:00:00 2001 From: Thomas Bruderer Date: Mon, 8 Jun 2020 12:26:55 +0200 Subject: [PATCH 07/10] All the helpers can be static --- Funcky.Test/EnumerableExtensionTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Funcky.Test/EnumerableExtensionTest.cs b/Funcky.Test/EnumerableExtensionTest.cs index ac5e3c22..1ebfbb8b 100644 --- a/Funcky.Test/EnumerableExtensionTest.cs +++ b/Funcky.Test/EnumerableExtensionTest.cs @@ -112,7 +112,7 @@ private static Option SquareEvenNumbers(int number) private static bool IsEven(int number) => number % 2 == 0; - private void AcceptIntegers(IEnumerable values) + private static void AcceptIntegers(IEnumerable values) { foreach (var value in values) { @@ -120,7 +120,7 @@ private void AcceptIntegers(IEnumerable values) } } - private void AcceptUnits(IEnumerable units) + private static void AcceptUnits(IEnumerable units) { foreach (var unit in units) { @@ -128,14 +128,14 @@ private void AcceptUnits(IEnumerable units) } } - private bool ExpectedOptionValue(List valueEnumerable) => + private static bool ExpectedOptionValue(List valueEnumerable) => valueEnumerable.Count switch { 0 => false, _ => true, }; - private void ExpectedSingleOrNoneBehaviour(List list, Func singleOrNone) + private static void ExpectedSingleOrNoneBehaviour(List list, Func singleOrNone) { switch (list.Count) { From 0c182a22f4654abd78f3a59947d8577422f6245a Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 8 Jun 2020 13:31:39 +0200 Subject: [PATCH 08/10] Update Funcky.Test/EnumerableExtensionTest.cs Co-authored-by: Ruben Schmidmeister --- Funcky.Test/EnumerableExtensionTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Funcky.Test/EnumerableExtensionTest.cs b/Funcky.Test/EnumerableExtensionTest.cs index 1ebfbb8b..62dc426e 100644 --- a/Funcky.Test/EnumerableExtensionTest.cs +++ b/Funcky.Test/EnumerableExtensionTest.cs @@ -128,7 +128,7 @@ private static void AcceptUnits(IEnumerable units) } } - private static bool ExpectedOptionValue(List valueEnumerable) => + private static bool ExpectedOptionValue(List valueEnumerable) => valueEnumerable.Count switch { 0 => false, From 9f709db1d07328ffa7a36b8b526aae846134eb60 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 8 Jun 2020 13:31:46 +0200 Subject: [PATCH 09/10] Update Funcky.Test/EnumerableExtensionTest.cs Co-authored-by: Ruben Schmidmeister --- Funcky.Test/EnumerableExtensionTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Funcky.Test/EnumerableExtensionTest.cs b/Funcky.Test/EnumerableExtensionTest.cs index 62dc426e..d7c2c3a6 100644 --- a/Funcky.Test/EnumerableExtensionTest.cs +++ b/Funcky.Test/EnumerableExtensionTest.cs @@ -85,7 +85,7 @@ public void WhereSelectFiltersEmptyValues() public void GivenAnValueEnumerableFirstLastOrNoneGivesTheCorrectOption(List valueEnumerable, List referenceEnumerable) { Assert.Equal(ExpectedOptionValue(valueEnumerable), valueEnumerable.FirstOrNone().Match(false, True)); - Assert.Equal(ExpectedOptionValue(valueEnumerable), referenceEnumerable.FirstOrNone().Match(false, True)); + Assert.Equal(ExpectedOptionValue(referenceEnumerable), referenceEnumerable.FirstOrNone().Match(false, True)); Assert.Equal(ExpectedOptionValue(valueEnumerable), valueEnumerable.LastOrNone().Match(false, True)); Assert.Equal(ExpectedOptionValue(valueEnumerable), referenceEnumerable.LastOrNone().Match(false, True)); From 27120568c9407453aeb6face4c11fa434081da16 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 8 Jun 2020 13:31:56 +0200 Subject: [PATCH 10/10] Update Funcky.Test/EnumerableExtensionTest.cs Co-authored-by: Ruben Schmidmeister --- Funcky.Test/EnumerableExtensionTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Funcky.Test/EnumerableExtensionTest.cs b/Funcky.Test/EnumerableExtensionTest.cs index d7c2c3a6..90dd5b4d 100644 --- a/Funcky.Test/EnumerableExtensionTest.cs +++ b/Funcky.Test/EnumerableExtensionTest.cs @@ -88,7 +88,7 @@ public void GivenAnValueEnumerableFirstLastOrNoneGivesTheCorrectOption(List Assert.Equal(ExpectedOptionValue(referenceEnumerable), referenceEnumerable.FirstOrNone().Match(false, True)); Assert.Equal(ExpectedOptionValue(valueEnumerable), valueEnumerable.LastOrNone().Match(false, True)); - Assert.Equal(ExpectedOptionValue(valueEnumerable), referenceEnumerable.LastOrNone().Match(false, True)); + Assert.Equal(ExpectedOptionValue(referenceEnumerable), referenceEnumerable.LastOrNone().Match(false, True)); } [Theory]