Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for inspecting the error case of alternative monads #735

Merged
merged 3 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Funcky.Test/Monads/EitherTest.Convenience.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ public void InspectReturnsOriginalValue(Either<string, int> either)
Assert.Equal(either, either.Inspect(NoOperation));
}

[Fact]
public void InspectLeftDoesNothingWhenEitherIsRight()
{
var either = Either<string, int>.Right(10);
either.InspectLeft(_ => throw new XunitException("Side effect was unexpectedly called"));
}

[Fact]
public void InspectLeftCallsSideEffectWhenEitherIsLeft()
{
const string value = "foo";
var either = Either<string, int>.Left(value);

var sideEffect = Option<string>.None;
either.InspectLeft(v => sideEffect = v);
FunctionalAssert.Some(value, sideEffect);
}

[Theory]
[MemberData(nameof(LeftAndRight))]
public void InspectLeftReturnsOriginalValue(Either<string, int> either)
{
Assert.Equal(either, either.InspectLeft(NoOperation));
}

[Fact]
public void GivenARightCaseTheGetOrElseFuncIsNotExecuted()
{
Expand Down
37 changes: 37 additions & 0 deletions Funcky.Test/Monads/OptionTest.Convenience.cs
Original file line number Diff line number Diff line change
@@ -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<int>.None;

var sideEffect = false;
option.InspectNone(() => sideEffect = true);
Assert.True(sideEffect);
}

[Theory]
[MemberData(nameof(SomeAndNone))]
public void InspectLeftReturnsOriginalValue(Option<int> option)
{
Assert.Equal(option, option.InspectNone(NoOperation));
}

public static TheoryData<Option<int>> SomeAndNone()
=> new()
{
Option<int>.None,
Option.Some(42),
};
}
31 changes: 31 additions & 0 deletions Funcky.Test/Monads/ResultTest.Convenience.cs
Original file line number Diff line number Diff line change
@@ -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<string>.Error(exception);

var sideEffect = Option<Exception>.None;
result.InspectError(v => sideEffect = v);
FunctionalAssert.Some(exception, sideEffect);
}

[Theory]
[MemberData(nameof(OkAndError))]
public void InspectErrorReturnsOriginalValue(Result<int> result)
{
Assert.Equal(result, result.InspectError(NoOperation));
}
}
10 changes: 5 additions & 5 deletions Funcky.Test/Monads/ResultTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,25 +176,25 @@ public void ErrorConstructorLeavesExistingStackTraceUnchanged()
{
var result = InterestingStackTrace(1);
var expectedStackTraceString = FunctionalAssert.Error(result).StackTrace;
_ = result.Match(ok: Result.Ok, error: Result<int>.Error);
_ = result.InspectError(error => Result<int>.Error(error));
var stackTraceString = FunctionalAssert.Error(result).StackTrace;
Assert.Equal(expectedStackTraceString, stackTraceString);
}

[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<int>.Error(new Exception("Any")).SelectMany(i => Result.Ok(i + 1)));

[Fact]
public void SelectManyReturnErrorResultWithOkResultMatchesTherightValue()
public void SelectManyReturnErrorResultWithOkResultMatchesTheRightValue()
=> FunctionalAssert.Error(Result.Ok(1).SelectMany(_ => Result<int>.Error(new Exception("Any"))));

[Fact]
public void SelectManyReturnErrorResultWithErrorResultMatchesTherightValue()
public void SelectManyReturnErrorResultWithErrorResultMatchesTheRightValue()
=> FunctionalAssert.Error(Result<int>.Error(new Exception("Any")).SelectMany(_ => Result<int>.Error(new Exception("Other"))));

[Fact]
Expand Down
7 changes: 7 additions & 0 deletions Funcky/Monads/Either/Either.Convenience.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ public Either<TLeft, TRight> OrElse(Either<TLeft, TRight> fallback)
public Either<TLeft, TRight> OrElse(Func<TLeft, Either<TLeft, TRight>> fallback)
=> Match(left: fallback, right: Either<TLeft>.Return);

/// <summary>Performs a side effect when the either is left and returns the either value again.</summary>
public Either<TLeft, TRight> InspectLeft(Action<TLeft> inspector)
bash marked this conversation as resolved.
Show resolved Hide resolved
{
Switch(left: inspector, right: NoOperation);
return this;
}

/// <remarks>Careful! This overload discards the left value.</remarks>
[Pure]
public TRight GetOrElse(TRight fallback)
Expand Down
9 changes: 9 additions & 0 deletions Funcky/Monads/Option/Option.Convenience.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ public Option<TItem> Inspect(Action<TItem> inspector)
return this;
}

/// <summary>
/// Performs a side effect when the option has no value (i.e. when the option is <see cref="None"/>) and returns the option again.
/// </summary>
public Option<TItem> InspectNone(Action inspector)
{
Switch(none: inspector, some: NoOperation);
return this;
}

/// <summary>
/// Returns an <see cref="IEnumerable{T}"/> that yields exactly one value when the option
/// has an item and nothing when the option is empty.
Expand Down
7 changes: 7 additions & 0 deletions Funcky/Monads/Result/Result.Convenience.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public TValidResult GetOrElse(TValidResult fallback)
public TValidResult GetOrElse(Func<Exception, TValidResult> fallback)
=> Match(error: fallback, ok: Identity);

/// <summary>Performs a side effect when the result is error and returns the result again.</summary>
public Result<TValidResult> InspectError(Action<Exception> inspector)
{
Switch(ok: NoOperation, error: inspector);
return this;
}

public TValidResult GetOrThrow()
=> GetOrElse(ThrowWithOriginalStackTrace);

Expand Down
3 changes: 3 additions & 0 deletions Funcky/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#nullable enable
Funcky.Monads.Either<TLeft, TRight>.InspectLeft(System.Action<TLeft>! inspector) -> Funcky.Monads.Either<TLeft, TRight>
Funcky.Monads.Either<TLeft, TRight>.OrElse(Funcky.Monads.Either<TLeft, TRight> fallback) -> Funcky.Monads.Either<TLeft, TRight>
Funcky.Monads.Either<TLeft, TRight>.OrElse(System.Func<TLeft, Funcky.Monads.Either<TLeft, TRight>>! fallback) -> Funcky.Monads.Either<TLeft, TRight>
Funcky.Monads.Option<TItem>.InspectNone(System.Action! inspector) -> Funcky.Monads.Option<TItem>
Funcky.Monads.Result<TValidResult>.GetOrElse(System.Func<System.Exception!, TValidResult>! fallback) -> TValidResult
Funcky.Monads.Result<TValidResult>.GetOrElse(TValidResult fallback) -> TValidResult
Funcky.Monads.Result<TValidResult>.InspectError(System.Action<System.Exception!>! inspector) -> Funcky.Monads.Result<TValidResult>
Funcky.Monads.Result<TValidResult>.OrElse(Funcky.Monads.Result<TValidResult> fallback) -> Funcky.Monads.Result<TValidResult>
Funcky.Monads.Result<TValidResult>.OrElse(System.Func<System.Exception!, Funcky.Monads.Result<TValidResult>>! fallback) -> Funcky.Monads.Result<TValidResult>