From 03bad94d7da999c2b91c8fc4b9baf6ab564d7184 Mon Sep 17 00:00:00 2001 From: trippwill <5862883+trippwill@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:04:58 +0000 Subject: [PATCH 1/2] Fix nullable bugs in Select and From Updated the `Opt` class in `Opt.cs` to include XML documentation for the `From` method, which now supports both nullable value types and non-nullable reference types. Converted `Zip` and `Flatten` methods to extension methods. Changed the constructor of `Opt` to internal and modified the `Select` method to accept functions returning nullable types. Added a new `Where` method for filtering options based on predicates. In `OptTests.cs`, added unit tests for the `Select` and `Where` methods, covering value transformations, null handling, and behavior when the option is `None`. Included additional tests for casting options to validate correct and incorrect casts. --- src/libanvl.Opt/Opt.cs | 37 +++++++++++++--- test/libanvl.Opt.Test/OptTests.cs | 74 +++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/libanvl.Opt/Opt.cs b/src/libanvl.Opt/Opt.cs index a906566..d94099a 100644 --- a/src/libanvl.Opt/Opt.cs +++ b/src/libanvl.Opt/Opt.cs @@ -33,6 +33,14 @@ public static class Opt /// An containing the value if not null, otherwise . public static Opt From(T? value) where T : notnull => value is null ? Opt.None : Some(value); + /// + /// Creates an from a nullable value. + /// + /// The type of the value. + /// The nullable value to wrap. + /// An containing the value if not null, otherwise . + public static Opt From(T? value) where T : struct => value.HasValue ? Some(value.Value) : Opt.None; + /// /// Combines two options into an option containing a tuple of their values if both are present. /// @@ -41,7 +49,7 @@ public static class Opt /// The first option. /// The second option. /// An option containing a tuple of the values if both are present, otherwise none. - public static Opt<(T, U)> Zip(Opt opt1, Opt opt2) + public static Opt<(T, U)> Zip(this Opt opt1, Opt opt2) where T : notnull where U : notnull { @@ -55,7 +63,7 @@ public static class Opt /// /// The option to flatten. /// The flattened option. - public static Opt Flatten(Opt> opt) + public static Opt Flatten(this Opt> opt) where T : notnull => opt.IsSome ? opt.Unwrap() : Opt.None; /// @@ -102,7 +110,7 @@ public static Opt AndThen(this Opt opt, Func> fn) where T /// /// The value to wrap. /// Thrown if the value is null. - public Opt(T value) + internal Opt(T value) { OptException.ThrowIfNull(value); _value = value; @@ -248,10 +256,29 @@ public TResult Match(Func some, Func none) => IsSo /// The type of the result. /// The function to transform the value. /// An containing the transformed value or none. - public Opt Select(Func fn) where U : notnull => IsSome - ? new(fn(OptException.ThrowInternalErrorIfNull(_value))) + public Opt Select(Func fn) where U : notnull => IsSome + ? Opt.From(fn(OptException.ThrowInternalErrorIfNull(_value))) : Opt.None; + /// + /// Transforms the value if present using the specified function. + /// + /// The type of the result. + /// The function to transform the value. + /// An containing the transformed value or none. + public Opt Select(Func fn) where U: struct => IsSome + ? Opt.From(fn(OptException.ThrowInternalErrorIfNull(_value))) + : Opt.None; + + /// + /// Filters the option based on a predicate. + /// + /// The predicate to apply. + /// The option if the predicate is true, otherwise none. + public Opt Where(Func predicate) => IsSome && predicate(OptException.ThrowInternalErrorIfNull(_value)) + ? this + : None; + /// /// Casts the value to the specified type if present. /// diff --git a/test/libanvl.Opt.Test/OptTests.cs b/test/libanvl.Opt.Test/OptTests.cs index 8a18b21..bf141e7 100644 --- a/test/libanvl.Opt.Test/OptTests.cs +++ b/test/libanvl.Opt.Test/OptTests.cs @@ -268,4 +268,78 @@ public void GetHashCode_ReturnsSameHashCode_ForNone() var opt2 = Opt.None; Assert.Equal(opt1.GetHashCode(), opt2.GetHashCode()); } + + [Fact] + public void Select_ReturnsTransformedOption_WhenSome() + { + var opt = Opt.Some(5); + var result = opt.Select(x => x * 2); + Assert.True(result.IsSome); + Assert.Equal(10, result.Unwrap()); + } + + [Fact] + public void Select_ReturnsNone_WhenTransformedValueIsNull() + { + var opt = Opt.Some("Hello"); + var result = opt.Select(x => null); + Assert.True(result.IsNone); + } + + [Fact] + public void Select_ReturnsNone_WhenTransformedValueIsNullValueType() + { + Opt opt = 5; + var result = opt.Select(_ => null); + Assert.True(result.IsNone); + } + + [Fact] + public void Select_ReturnsNone_WhenNone() + { + var opt = Opt.None; + var result = opt.Select(x => x * 2); + Assert.True(result.IsNone); + } + + [Fact] + public void Where_ReturnsSome_WhenPredicateIsTrue() + { + var opt = Opt.Some(5); + var result = opt.Where(x => x > 3); + Assert.True(result.IsSome); + } + + [Fact] + public void Where_ReturnsNone_WhenPredicateIsFalse() + { + var opt = Opt.Some(5); + var result = opt.Where(x => x < 3); + Assert.True(result.IsNone); + } + + [Fact] + public void Where_ReturnsNone_WhenOptionIsNone() + { + var opt = Opt.None; + var result = opt.Where(x => x > 3); + Assert.True(result.IsNone); + } + + [Fact] + public void Cast_ReturnsSome_WhenCastIsValid() + { + var opt = Opt.Some((object)5); + var result = opt.Cast(); + Assert.True(result.IsSome); + Assert.Equal(5, result.Unwrap()); + } + + [Fact] + public void Cast_ReturnsNone_WhenCastIsInvalid() + { + var opt = Opt.Some((object)"string"); + var result = opt.Cast(); + Assert.True(result.IsNone); + } } From 74bcfc9bf05f2c5c320256fd53b3d9f77139fac1 Mon Sep 17 00:00:00 2001 From: Charles Willis <5862883+trippwill@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:59:17 +0000 Subject: [PATCH 2/2] Enhance Opt class with new transformation methods (#24) Updated `Opt.cs` to add overloaded `AndThen` methods supporting nullable return types and introduced new `OrThen` methods for option handling. Added unit tests in `OptTests.cs` to validate the new functionality for both some and none cases. --- src/libanvl.Opt/Opt.cs | 83 +++++++++++++++++++++++++++++-- test/libanvl.Opt.Test/OptTests.cs | 52 ++++++++++++++++++- 2 files changed, 128 insertions(+), 7 deletions(-) diff --git a/src/libanvl.Opt/Opt.cs b/src/libanvl.Opt/Opt.cs index d94099a..cfaba73 100644 --- a/src/libanvl.Opt/Opt.cs +++ b/src/libanvl.Opt/Opt.cs @@ -1,7 +1,5 @@ -using libanvl.Exceptions; -using System.Collections; -using System; -using System.Runtime.Serialization; +using System.Collections; +using libanvl.Exceptions; namespace libanvl; @@ -86,10 +84,85 @@ public static Opt Filter(this Opt opt, Func predicate) where T /// The option to transform. /// The function to transform the value. /// The transformed option. - public static Opt AndThen(this Opt opt, Func> fn) where T : notnull where U : notnull + public static Opt AndThen(this Opt opt, Func> fn) + where T : notnull + where U : notnull { return opt.IsSome ? fn(opt.Unwrap()) : Opt.None; } + + /// + /// Transforms the option using a function that returns another option. + /// + /// The type of the value. + /// The type of the result. + /// The option to transform. + /// The function to transform the value. + /// The transformed option. + public static Opt AndThen(this Opt opt, Func fn) + where T : notnull + where U : notnull + { + return opt.IsSome ? From(fn(opt.Unwrap())) : Opt.None; + } + + /// + /// Transforms the option using a function that returns another option. + /// + /// The type of the value. + /// The type of the result. + /// The option to transform. + /// The function to transform the value. + /// The transformed option. + public static Opt AndThen(this Opt opt, Func fn) + where T : notnull + where U : struct + { + return opt.IsSome ? From(fn(opt.Unwrap())) : Opt.None; + } + + /// + /// Returns the first option if it has a value, otherwise returns the result of the specified function. + /// + /// The type of the value. + /// The type of the state. + /// The option to check. + /// The state to pass to the function if the option does not have a value. + /// The function to invoke if the option does not have a value. + /// The first option if it has a value, otherwise the result of the function. + public static Opt OrThen(this Opt opt, U state, Func fn) + where T : notnull + { + return opt.IsSome ? opt : From(fn(state)); + } + + /// + /// Returns the first option if it has a value, otherwise returns the result of the specified function. + /// + /// The type of the value. + /// The type of the state. + /// The option to check. + /// The state to pass to the function if the option does not have a value. + /// The function to invoke if the option does not have a value. + /// The first option if it has a value, otherwise the result of the function. + public static Opt OrThen(this Opt opt, U state, Func fn) + where T : struct + { + return opt.IsSome ? opt : From(fn(state)); + } + + /// + /// Returns the first option if it has a value, otherwise returns the second option. + /// + /// The type of the value. + /// The first option. + /// The second option. + /// The first option if it has a value, otherwise the second option. + public static Opt OrThen(this Opt left, Opt right) + where T : notnull + { + return left.IsSome ? left : right; + } } /// diff --git a/test/libanvl.Opt.Test/OptTests.cs b/test/libanvl.Opt.Test/OptTests.cs index bf141e7..f5e365f 100644 --- a/test/libanvl.Opt.Test/OptTests.cs +++ b/test/libanvl.Opt.Test/OptTests.cs @@ -82,6 +82,7 @@ public void AndThen_ReturnsNone_WhenNone() var result = opt.AndThen(x => Opt.Some(x * 2)); Assert.True(result.IsNone); } + [Fact] public void CompareTo_ReturnsNegative_WhenThisIsSomeAndOtherIsNone() { @@ -311,13 +312,60 @@ public void Where_ReturnsSome_WhenPredicateIsTrue() } [Fact] - public void Where_ReturnsNone_WhenPredicateIsFalse() + public void AndThen_WithNullableFunc_ReturnsTransformedOption_WhenSome() { var opt = Opt.Some(5); - var result = opt.Where(x => x < 3); + var result = opt.AndThen(x => (int?)(x * 2)); + Assert.True(result.IsSome); + Assert.Equal(10, result.Unwrap()); + } + + [Fact] + public void AndThen_WithNullableFunc_ReturnsNone_WhenNone() + { + var opt = Opt.None; + var result = opt.AndThen(x => (int?)(x * 2)); Assert.True(result.IsNone); } + [Fact] + public void OrThen_WithFunc_ReturnsFirstOption_WhenSome() + { + var opt = Opt.Some(5); + var result = opt.OrThen("state", state => (int?)10); + Assert.True(result.IsSome); + Assert.Equal(5, result.Unwrap()); + } + + [Fact] + public void OrThen_WithFunc_ReturnsTransformedOption_WhenNone() + { + var opt = Opt.None; + var result = opt.OrThen("state", state => (int?)10); + Assert.True(result.IsSome); + Assert.Equal(10, result.Unwrap()); + } + + [Fact] + public void OrThen_WithOption_ReturnsFirstOption_WhenSome() + { + var opt1 = Opt.Some(5); + var opt2 = Opt.Some(10); + var result = opt1.OrThen(opt2); + Assert.True(result.IsSome); + Assert.Equal(5, result.Unwrap()); + } + + [Fact] + public void OrThen_WithOption_ReturnsSecondOption_WhenNone() + { + var opt1 = Opt.None; + var opt2 = Opt.Some(10); + var result = opt1.OrThen(opt2); + Assert.True(result.IsSome); + Assert.Equal(10, result.Unwrap()); + } + [Fact] public void Where_ReturnsNone_WhenOptionIsNone() {