From dddefa01b1a008640b9f13823a24f6fa6a84144f Mon Sep 17 00:00:00 2001 From: Ruben Schmidmeister <4602612+bash@users.noreply.github.com> Date: Mon, 10 Jul 2023 12:27:39 +0200 Subject: [PATCH 1/3] Add methods for inspecting the error case of alternative monads --- Funcky.Test/Monads/EitherTest.Convenience.cs | 25 +++++++++++++ Funcky.Test/Monads/OptionTest.Convenience.cs | 37 ++++++++++++++++++++ Funcky.Test/Monads/ResultTest.Convenience.cs | 31 ++++++++++++++++ Funcky/Monads/Either/Either.Convenience.cs | 7 ++++ Funcky/Monads/Option/Option.Convenience.cs | 9 +++++ Funcky/Monads/Result/Result.Convenience.cs | 7 ++++ Funcky/PublicAPI.Unshipped.txt | 3 ++ 7 files changed, 119 insertions(+) create mode 100644 Funcky.Test/Monads/OptionTest.Convenience.cs create mode 100644 Funcky.Test/Monads/ResultTest.Convenience.cs diff --git a/Funcky.Test/Monads/EitherTest.Convenience.cs b/Funcky.Test/Monads/EitherTest.Convenience.cs index ac7d978c..19c8bb4f 100644 --- a/Funcky.Test/Monads/EitherTest.Convenience.cs +++ b/Funcky.Test/Monads/EitherTest.Convenience.cs @@ -31,6 +31,31 @@ public void InspectReturnsOriginalValue(Either either) Assert.Equal(either, either.Inspect(NoOperation)); } + [Fact] + public void InspectLeftDoesNothingWhenEitherIsRight() + { + var either = Either.Right(10); + either.InspectLeft(_ => throw new XunitException("Side effect was unexpectedly called")); + } + + [Fact] + public void InspectLeftCallsSideEffectWhenEitherIsLeft() + { + const string value = "foo"; + var either = Either.Left(value); + + var sideEffect = Option.None; + either.InspectLeft(v => sideEffect = v); + FunctionalAssert.Some(value, sideEffect); + } + + [Theory] + [MemberData(nameof(LeftAndRight))] + public void InspectLeftReturnsOriginalValue(Either either) + { + Assert.Equal(either, either.InspectLeft(NoOperation)); + } + [Fact] public void GivenARightCaseTheGetOrElseFuncIsNotExecuted() { diff --git a/Funcky.Test/Monads/OptionTest.Convenience.cs b/Funcky.Test/Monads/OptionTest.Convenience.cs new file mode 100644 index 00000000..15349058 --- /dev/null +++ b/Funcky.Test/Monads/OptionTest.Convenience.cs @@ -0,0 +1,37 @@ +using Xunit.Sdk; + +namespace Funcky.Test.Monads; + +public sealed partial class OptionTest +{ + [Fact] + public void InspectNoneDoesNothingWhenOptionIsNone() + { + var option = Option.Some(10); + option.InspectNone(() => throw new XunitException("Side effect was unexpectedly called")); + } + + [Fact] + public void InspectNoneCallsSideEffectWhenOptionIsNone() + { + var option = Option.None; + + var sideEffect = false; + option.InspectNone(() => sideEffect = true); + Assert.True(sideEffect); + } + + [Theory] + [MemberData(nameof(SomeAndNone))] + public void InspectLeftReturnsOriginalValue(Option option) + { + Assert.Equal(option, option.InspectNone(NoOperation)); + } + + public static TheoryData> SomeAndNone() + => new() + { + Option.None, + Option.Some(42), + }; +} diff --git a/Funcky.Test/Monads/ResultTest.Convenience.cs b/Funcky.Test/Monads/ResultTest.Convenience.cs new file mode 100644 index 00000000..abd5e7c0 --- /dev/null +++ b/Funcky.Test/Monads/ResultTest.Convenience.cs @@ -0,0 +1,31 @@ +using Xunit.Sdk; + +namespace Funcky.Test.Monads; + +public sealed partial class ResultTest +{ + [Fact] + public void InspectErrorDoesNothingWhenResultIsOk() + { + var result = Result.Ok("foo"); + result.InspectError(_ => throw new XunitException("Side effect was unexpectedly called")); + } + + [Fact] + public void InspectErrorCallsSideEffectWhenResultIsError() + { + var exception = new Exception("Bam!"); + var result = Result.Error(exception); + + var sideEffect = Option.None; + result.InspectError(v => sideEffect = v); + FunctionalAssert.Some(exception, sideEffect); + } + + [Theory] + [MemberData(nameof(OkAndError))] + public void InspectErrorReturnsOriginalValue(Result result) + { + Assert.Equal(result, result.InspectError(NoOperation)); + } +} diff --git a/Funcky/Monads/Either/Either.Convenience.cs b/Funcky/Monads/Either/Either.Convenience.cs index 6186afd3..97c5a1a0 100644 --- a/Funcky/Monads/Either/Either.Convenience.cs +++ b/Funcky/Monads/Either/Either.Convenience.cs @@ -25,6 +25,13 @@ public Either OrElse(Either fallback) public Either OrElse(Func> fallback) => Match(left: fallback, right: Either.Return); + /// Performs a side effect when the either is left and returns the either value again. + public Either InspectLeft(Action inspector) + { + Switch(left: inspector, right: NoOperation); + return this; + } + /// Careful! This overload discards the left value. [Pure] public TRight GetOrElse(TRight fallback) diff --git a/Funcky/Monads/Option/Option.Convenience.cs b/Funcky/Monads/Option/Option.Convenience.cs index 964ce492..80deeb59 100644 --- a/Funcky/Monads/Option/Option.Convenience.cs +++ b/Funcky/Monads/Option/Option.Convenience.cs @@ -51,6 +51,15 @@ public Option Inspect(Action inspector) return this; } + /// + /// Performs a side effect when the option has no value (i.e. when the option is ) and returns the option again. + /// + public Option InspectNone(Action inspector) + { + Switch(none: inspector, some: NoOperation); + return this; + } + /// /// Returns an that yields exactly one value when the option /// has an item and nothing when the option is empty. diff --git a/Funcky/Monads/Result/Result.Convenience.cs b/Funcky/Monads/Result/Result.Convenience.cs index 44aa4772..83bb2c09 100644 --- a/Funcky/Monads/Result/Result.Convenience.cs +++ b/Funcky/Monads/Result/Result.Convenience.cs @@ -31,6 +31,13 @@ public TValidResult GetOrElse(TValidResult fallback) public TValidResult GetOrElse(Func fallback) => Match(error: fallback, ok: Identity); + /// Performs a side effect when the result is error and returns the result again. + public Result InspectError(Action inspector) + { + Switch(ok: NoOperation, error: inspector); + return this; + } + public TValidResult GetOrThrow() => GetOrElse(ThrowWithOriginalStackTrace); diff --git a/Funcky/PublicAPI.Unshipped.txt b/Funcky/PublicAPI.Unshipped.txt index a1559e56..ec095891 100644 --- a/Funcky/PublicAPI.Unshipped.txt +++ b/Funcky/PublicAPI.Unshipped.txt @@ -1,7 +1,10 @@ #nullable enable +Funcky.Monads.Either.InspectLeft(System.Action! inspector) -> Funcky.Monads.Either Funcky.Monads.Either.OrElse(Funcky.Monads.Either fallback) -> Funcky.Monads.Either Funcky.Monads.Either.OrElse(System.Func>! fallback) -> Funcky.Monads.Either +Funcky.Monads.Option.InspectNone(System.Action! inspector) -> Funcky.Monads.Option Funcky.Monads.Result.GetOrElse(System.Func! fallback) -> TValidResult Funcky.Monads.Result.GetOrElse(TValidResult fallback) -> TValidResult +Funcky.Monads.Result.InspectError(System.Action! inspector) -> Funcky.Monads.Result Funcky.Monads.Result.OrElse(Funcky.Monads.Result fallback) -> Funcky.Monads.Result Funcky.Monads.Result.OrElse(System.Func>! fallback) -> Funcky.Monads.Result From 669af64abfbbda8740311c1129ab033cbc585804 Mon Sep 17 00:00:00 2001 From: Ruben Schmidmeister <4602612+bash@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:20:20 +0200 Subject: [PATCH 2/3] Use Inspect instead of Match --- Funcky.Test/Monads/ResultTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Funcky.Test/Monads/ResultTest.cs b/Funcky.Test/Monads/ResultTest.cs index 48cd483a..d4e7e272 100644 --- a/Funcky.Test/Monads/ResultTest.cs +++ b/Funcky.Test/Monads/ResultTest.cs @@ -176,7 +176,7 @@ public void ErrorConstructorLeavesExistingStackTraceUnchanged() { var result = InterestingStackTrace(1); var expectedStackTraceString = FunctionalAssert.Error(result).StackTrace; - _ = result.Match(ok: Result.Ok, error: Result.Error); + _ = result.InspectError(error => Result.Error(error)); var stackTraceString = FunctionalAssert.Error(result).StackTrace; Assert.Equal(expectedStackTraceString, stackTraceString); } From 6c5c87806ecee6b65bd025b78cc5d9d9482232af Mon Sep 17 00:00:00 2001 From: Ruben Schmidmeister <4602612+bash@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:20:53 +0200 Subject: [PATCH 3/3] Fix typo --- Funcky.Test/Monads/ResultTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Funcky.Test/Monads/ResultTest.cs b/Funcky.Test/Monads/ResultTest.cs index d4e7e272..42dfdbd6 100644 --- a/Funcky.Test/Monads/ResultTest.cs +++ b/Funcky.Test/Monads/ResultTest.cs @@ -182,19 +182,19 @@ public void ErrorConstructorLeavesExistingStackTraceUnchanged() } [Fact] - public void SelectManyWithOkResultMatchesTherightValue() + public void SelectManyWithOkResultMatchesTheRightValue() => FunctionalAssert.Ok(2, Result.Ok(1).SelectMany(i => Result.Ok(i + 1))); [Fact] - public void SelectManyWithErrorResultMatchesTherightValue() + public void SelectManyWithErrorResultMatchesTheRightValue() => FunctionalAssert.Error(Result.Error(new Exception("Any")).SelectMany(i => Result.Ok(i + 1))); [Fact] - public void SelectManyReturnErrorResultWithOkResultMatchesTherightValue() + public void SelectManyReturnErrorResultWithOkResultMatchesTheRightValue() => FunctionalAssert.Error(Result.Ok(1).SelectMany(_ => Result.Error(new Exception("Any")))); [Fact] - public void SelectManyReturnErrorResultWithErrorResultMatchesTherightValue() + public void SelectManyReturnErrorResultWithErrorResultMatchesTheRightValue() => FunctionalAssert.Error(Result.Error(new Exception("Any")).SelectMany(_ => Result.Error(new Exception("Other")))); [Fact]