Skip to content

Commit

Permalink
feat(Result): extension methods to enumerables of Result
Browse files Browse the repository at this point in the history
  • Loading branch information
MrBogomips committed Feb 12, 2024
1 parent 772905d commit b0ba796
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 136 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Bogoware Monads Changelog

## 9.0.0

### New Features

- The following extension methods on `IEnumerable<Result<T>>` have been introduced:
- `MapEach`: maps each `Result` in the sequence, preserving the failed `Result`s
- `BindEach`: binds each `Result` in the sequence, preserving the failed `Result`s
- `MatchEach`: matches each `Result` in the sequence
- `AggregateResult`: transforms a sequence of `Result`s into a single `Result` that contains a sequence of the successful values. If the original sequence contains any `Error` then will return a failed `Result` with an `AggregateError` containing all the errors found.

### Breaking Changes
- The following extension methods on `IEnumerable<Result<T>>` have been removed:
- `Map`: use `MapEach` instead. The latter will preserve the failed `Result`s
- `Bind`: use `BindEach` instead. The latter will preserve the failed `Result`s
- `Macth` renamed to `MatchEach`.
1 change: 1 addition & 0 deletions Monads.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
LICENSE = LICENSE
Monads.sln.DotSettings = Monads.sln.DotSettings
Monads.sln.DotSettings.user = Monads.sln.DotSettings.user
CHANGELOG.md = CHANGELOG.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monads", "src\Monads\Monads.csproj", "{DF5B4155-880E-4E7E-AC80-905C15D62E18}"
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ public Result<Unit> Publish() => Result
.ExecuteIfSuccess(() => PublishingStatus = PublishingStatus.Published);
```

## Manipulating `IEnumerable<Result<T>>`

The library provide a set of extension methods that enable manipulation of sequences of `Result<T>` instances.

* `MapEach`: Maps each `Result` in the sequence, preserving the failed `Result`s
* `BindEach`: Binds each `Result` in the sequence, preserving the failed `Result`s
* `MatchEach`: Matches each `Result` in the sequence
* `AggregateResult`: Transforms a sequence of `Result`s into a single `Result` that contains a sequence of the successful values. If the original sequence contains any `Error` then will return a failed `Result` with an `AggregateError` containing all the errors found.

## Design Goals for `Error`

The `Error` class is used for modeling errors and works in conjunction with the `Result<T>` monad.
Expand Down
2 changes: 1 addition & 1 deletion sample/ConsoleApp/Pipelines/GamblingPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Task<Result<Amount>> FourBetsInARow() => Result.Success(new Amount(initialAmount

var attempts = attemptsTasks.Select(task => task.Result);

var messages = attempts.Match(
var messages = attempts.MatchEach(
win => $"You won {win.Value}",
$"You lost {initialAmount}");

Expand Down
123 changes: 0 additions & 123 deletions src/Monads/Extensions/EnumerableResultExtensions.cs

This file was deleted.

1 change: 1 addition & 0 deletions src/Monads/Maybe/IMaybe.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ReSharper disable UnusedTypeParameter
namespace Bogoware.Monads;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Bogoware.Monads;

public static class EnumerableMaybeExtensions
public static class MaybeEnumerableExtensions
{
/// <summary>
/// Determines if all <see cref="Maybe{T}"/>s of a sequence are <c>Some</c>s.
Expand Down
6 changes: 3 additions & 3 deletions src/Monads/Result/IResult.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ReSharper disable UnusedMemberInSuper.Global
// ReSharper disable TypeParameterCanBeVariant
// ReSharper disable UnusedTypeParameter

namespace Bogoware.Monads;

Expand All @@ -10,7 +10,7 @@ public interface IResult
public Error GetErrorOrThrow();
}

public interface IResult<TValue> : IResult
public interface IResult<in TValue> : IResult
{
public TValue GetValueOrThrow();
//public TValue GetValueOrThrow();
}
34 changes: 34 additions & 0 deletions src/Monads/Result/ResultCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Bogoware.Monads;

/// <summary>
/// Represents a collection of <see cref="Result{TValue}"/>s.
/// </summary>
internal class ResultCollection<TValue>
{
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Error GetErrorOrThrow() => _error ?? throw new ResultSuccessException();

private readonly List<Result<TValue>> _results;
private readonly AggregateError? _error;

internal ResultCollection(IEnumerable<Result<TValue>> results)
{
_results = [..results];
IsSuccess = _results.Count == 0
|| _results.All(r => r.IsSuccess);

if (IsSuccess) return;

var errors = _results.Where(r => r.IsFailure).Select(r => r.GetErrorOrThrow());
_error = new (errors);
}

internal Result<IEnumerable<TValue?>> ToResult()
{
if (IsFailure) return Result.Failure<IEnumerable<TValue?>>(_error);
if (_results.Count == 0) return Result.Success(Enumerable.Empty<TValue?>());
var values = _results.Select(r => r.Value);
return Result.Success(values);
}
}
30 changes: 30 additions & 0 deletions src/Monads/Result/ResultEnumerableExtensions.Filters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Runtime.CompilerServices;

namespace Bogoware.Monads;

public static partial class ResultEnumerableExtensions
{

/// <summary>
/// Filters <c>Success</c>es via the predicate.
/// <c>Failure</c>s are discarded.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<Result<TValue>> Where<TValue>(
this IEnumerable<Result<TValue>> successes, Func<TValue, bool> predicate)
=> successes.SelectValues()
.Where(predicate)
.Select(v => new Result<TValue>(v));

/// <summary>
/// Filters <c>Success</c>es via negated predicate.
/// <c>Failure</c>s are discarded.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<Result<TValue>> WhereNot<TValue>(
this IEnumerable<Result<TValue>> successes, Func<TValue, bool> predicate)
=> successes.SelectValues()
.Where(v => !predicate(v))
.Select(v => new Result<TValue>(v));

}
57 changes: 57 additions & 0 deletions src/Monads/Result/ResultEnumerableExtensions.Predicates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Runtime.CompilerServices;

// ReSharper disable MemberCanBePrivate.Global

namespace Bogoware.Monads;

public static partial class ResultEnumerableExtensions
{
/// <summary>
/// Determines if all <see cref="Result{TValue}"/>s of a sequence are <c>Success</c>s.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AllSuccess(this IEnumerable<IResult> successes)
=> successes.All(r => r.IsSuccess);

/// <inheritdoc cref="AllSuccess"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AllSuccess<TValue>(this IEnumerable<Result<TValue>> successes)
=> successes.All(v => v.IsSuccess);

/// <summary>
/// Determines if all <see cref="Result{TValue}"/>s of a sequence are <c>Failure</c>s.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AllFailure(this IEnumerable<IResult> successes)
=> successes.All(r => r.IsFailure);

/// <inheritdoc cref="AllFailure"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AllFailure<TValue>(this IEnumerable<Result<TValue>> successes)
=> successes.All(v => v.IsFailure);

/// <summary>
/// Determines if any <see cref="Result{TValue}"/> of a sequence is <c>Success</c>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AnySuccess(this IEnumerable<IResult> successes)
=> successes.Any(r => r.IsSuccess);

/// <inheritdoc cref="AnySuccess"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AnySuccess<TValue>(this IEnumerable<Result<TValue>> successes)
=> successes.Any(v => v.IsSuccess);

/// <summary>
/// Determines if any <see cref="Result{TValue}"/> of a sequence is <c>Failure</c>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AnyFailure(this IEnumerable<IResult> successes)
=> successes.Any(r => r.IsFailure);

/// <inheritdoc cref="AnyFailure"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AnyFailure<TValue>(this IEnumerable<Result<TValue>> successes)
=> successes.Any(v => v.IsFailure);

}
61 changes: 61 additions & 0 deletions src/Monads/Result/ResultEnumerableExtensions.SelectValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Runtime.CompilerServices;

namespace Bogoware.Monads;

public static partial class ResultEnumerableExtensions
{

/// <summary>
/// Extract values from <see cref="Result{TValue}"/>s.
/// <c>Failure</c>s are discarded.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<TValue> SelectValues<TValue>(this IEnumerable<Result<TValue>> successes)
=> successes.SelectMany(v => v);

/// <summary>
/// Maps values via the functor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<Result<TNewValue>> MapEach<TValue, TNewValue>(
this IEnumerable<Result<TValue>> results, Func<TValue, TNewValue> functor)
=> results.Select(result => result.Map(functor));

/// <summary>
/// Bind values via the functor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<Result<TNewValue>> BindEach<TValue, TNewValue>(
this IEnumerable<Result<TValue>> results, Func<TValue, Result<TNewValue>> functor)
=> results.Select(result => result.Bind(functor));

/// <summary>
/// Matches results.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<TResult> MatchEach<TValue, TResult>(
this IEnumerable<Result<TValue>> results,
Func<TValue, TResult> mapSuccesses,
Func<Error, TResult> mapFailures)
=> results.Select(result => result.Match(mapSuccesses, mapFailures));

/// <inheritdoc cref="MatchEach{TValue,TResult}(System.Collections.Generic.IEnumerable{Bogoware.Monads.Result{TValue}},System.Func{TValue,TResult},System.Func{Bogoware.Monads.Error,TResult})"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<TResult> MatchEach<TValue, TResult>(
this IEnumerable<Result<TValue>> results,
Func<TValue, TResult> mapSuccesses,
TResult failure)
=> results.Select(result => result.Match(mapSuccesses, failure));

/// <summary>
/// Aggregates an enumeration of Result into a Result of an enumeration.
/// If all <see cref="Result{TValue}"/>s are <c>Success</c> then return a <c>Success</c> <see cref="Result{TValue}"/>.
/// otherwise return a <c>Failure</c> with an <see cref="AggregateError"/>.
/// </summary>
/// <param name="results"></param>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Result<IEnumerable<TValue?>> AggregateResult<TValue>(this IEnumerable<Result<TValue>> results)
=> new ResultCollection<TValue>(results).ToResult();
}
Loading

0 comments on commit b0ba796

Please sign in to comment.