Skip to content

Commit

Permalink
Add either property (#50)
Browse files Browse the repository at this point in the history
* Add public either property
  • Loading branch information
inputfalken committed Sep 23, 2018
1 parent 63001ac commit 67da7e7
Show file tree
Hide file tree
Showing 48 changed files with 1,315 additions and 1,203 deletions.
5 changes: 1 addition & 4 deletions src/Lemonad.ErrorHandling/AsyncResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,8 @@ public AsyncResult<T, TError> Filter(Func<T, Task<bool>> predicate, Func<TError>
public AsyncResult<T, TError> Filter(Func<T, Task<bool>> predicate, Func<Maybe<T>, TError> errorSelector) =>
TaskResultFunctions.Filter(TaskResult, predicate, errorSelector);

/// <inheritdoc cref="Result{T,TError}.HasError" />
public Task<bool> HasError => TaskResultFunctions.HasError(TaskResult);

/// <inheritdoc cref="Result{T,TError}.HasValue" />
public Task<bool> HasValue => TaskResultFunctions.HasValue(TaskResult);
public Task<Either<T, TError>> Either => TaskResultFunctions.Either(TaskResult);

/// <inheritdoc cref="Result{T,TError}.Multiple" />
public AsyncResult<T, IReadOnlyList<TError>> Multiple(
Expand Down
70 changes: 70 additions & 0 deletions src/Lemonad.ErrorHandling/Either.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;

namespace Lemonad.ErrorHandling {
/// <summary>
/// Contains either <typeparamref name="T"/> or <typeparamref name="TError"/>.
/// </summary>
/// <typeparam name="T">
/// The value type.
/// </typeparam>
/// <typeparam name="TError">
/// The error type.
/// </typeparam>
public readonly struct Either<T, TError> {
/// <summary>
/// Is true if there's a <typeparamref name="T" /> in the current state of the <see cref="Result{T,TError}" />.
/// </summary>
public bool HasValue { get; }

/// <summary>
/// Is true if there's a <typeparamref name="TError" /> in the current state of the <see cref="Result{T,TError}" />.
/// </summary>
public bool HasError { get; }

/// <summary>
/// The potential <typeparamref name="TError"/>.
/// </summary>
/// <remarks>
/// Verify with <see cref="HasError"/> before using this, unless your certain that the error exists.
/// </remarks>
/// <example>
/// <code language="c#">
/// if (Either.HasError)
/// {
/// // Safe to use.
/// Console.WriteLine(Either.Error)
/// }
/// </code>
/// </example>
public TError Error { get; }

/// <summary>
/// The potential <typeparamref name="T"/>.
/// </summary>
/// <remarks>
/// Verify with <see cref="HasValue"/> before using this, unless your certain that the value exists.
/// </remarks>
/// <example>
/// <code language="c#">
/// if (Either.HasValue)
/// {
/// // Safe to use.
/// Console.WriteLine(Either.Value)
/// }
/// </code>
/// </example>
public T Value { get; }

public Either(in T value, in TError error, bool hasError, bool hasValue) {
if (hasError == hasValue)
throw new ArgumentException(
$"Can never have the same value {nameof(hasError)} ({hasError}) and {nameof(hasValue)} ({hasValue})."
);

HasValue = hasValue;
HasError = hasError;
Value = value;
Error = error;
}
}
}
67 changes: 32 additions & 35 deletions src/Lemonad.ErrorHandling/Internal/TaskResultFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ internal static async Task<Result<T, TError>> Filter<T, TError>(Task<Result<T, T
Func<T, Task<bool>> predicate,
Func<Maybe<T>, TError> errorSelector) {
var result = await source.ConfigureAwait(false);
if (result.HasError) return result.Error;
if (await predicate(result.Value)) return result.Value;
if (result.Either.HasError) return result.Either.Error;
if (await predicate(result.Either.Value)) return result.Either.Value;

return errorSelector(result.Value);
return errorSelector(result.Either.Value);
}

[Pure]
Expand Down Expand Up @@ -119,9 +119,9 @@ await FlatMap(await source.ConfigureAwait(false), flatMapSelector, resultSelecto

[Pure]
internal static async Task<Result<TResult, TError>> FlatMap<TResult, TError, T>(Result<T, TError> result,
Func<T, Task<Result<TResult, TError>>> flatSelector) => result.HasValue
? await flatSelector(result.Value).ConfigureAwait(false)
: ResultExtensions.Error<TResult, TError>(result.Error);
Func<T, Task<Result<TResult, TError>>> flatSelector) => result.Either.HasValue
? await flatSelector(result.Either.Value).ConfigureAwait(false)
: ResultExtensions.Error<TResult, TError>(result.Either.Error);

[Pure]
internal static Task<Result<TResult, TError>> FlatMap<TSelector, TResult, TError, T>(Result<T, TError> result,
Expand All @@ -135,12 +135,12 @@ internal static Task<Result<TResult, TError>> FlatMap<TSelector, TResult, TError
internal static async Task<Result<TResult, TError>> FlatMap<TResult, TErrorResult, TError, T>(
Result<T, TError> result, Func<T, Task<Result<TResult, TErrorResult>>> flatMapSelector,
Func<TErrorResult, TError> errorSelector) {
if (result.HasError) return ResultExtensions.Error<TResult, TError>(result.Error);
if (result.Either.HasError) return ResultExtensions.Error<TResult, TError>(result.Either.Error);
if (flatMapSelector == null) throw new ArgumentNullException(nameof(flatMapSelector));
var okSelector = await flatMapSelector(result.Value);
var okSelector = await flatMapSelector(result.Either.Value);

return okSelector.HasValue
? ResultExtensions.Value<TResult, TError>(okSelector.Value)
return okSelector.Either.HasValue
? ResultExtensions.Value<TResult, TError>(okSelector.Either.Value)
: okSelector.MapError(errorSelector);
}

Expand All @@ -162,15 +162,15 @@ internal static Task<Result<TResult, TError>> FlatMap<TFlatMap, TResult, TErrorR
[Pure]
internal static async Task<Result<T, TError>> Flatten<TResult, T, TError>(Result<T, TError> result,
Func<T, Task<Result<TResult, TError>>> selector) {
if (result.HasValue) {
if (result.Either.HasValue) {
if (selector == null) throw new ArgumentNullException(nameof(selector));
var okSelector = await selector(result.Value).ConfigureAwait(false);
if (okSelector.HasValue)
return result.Value;
return okSelector.Error;
var okSelector = await selector(result.Either.Value).ConfigureAwait(false);
if (okSelector.Either.HasValue)
return result.Either.Value;
return okSelector.Either.Error;
}

return ResultExtensions.Error<T, TError>(result.Error);
return ResultExtensions.Error<T, TError>(result.Either.Error);
}

[Pure]
Expand Down Expand Up @@ -201,16 +201,16 @@ internal static async Task<Result<T, TError>> Flatten<T, TError, TResult>(Task<R
internal static async Task<Result<T, TError>> Flatten<TResult, TErrorResult, T, TError>(
Result<T, TError> result, Func<T, Task<Result<TResult, TErrorResult>>> selector,
Func<TErrorResult, TError> errorSelector) {
if (result.HasValue) {
if (result.Either.HasValue) {
if (selector == null) throw new ArgumentNullException(nameof(selector));
var okSelector = await selector(result.Value).ConfigureAwait(false);
if (okSelector.HasValue)
return ResultExtensions.Value<T, TError>(result.Value);
var okSelector = await selector(result.Either.Value).ConfigureAwait(false);
if (okSelector.Either.HasValue)
return ResultExtensions.Value<T, TError>(result.Either.Value);
var tmpThis = result;
return okSelector.FullMap(x => tmpThis.Value, errorSelector);
return okSelector.FullMap(x => tmpThis.Either.Value, errorSelector);
}

return ResultExtensions.Error<T, TError>(result.Error);
return ResultExtensions.Error<T, TError>(result.Either.Error);
}

[Pure]
Expand Down Expand Up @@ -251,10 +251,10 @@ internal static async Task<Result<TResult, TErrorResult>> FullFlatMap<T, TError,
internal static async Task<Result<TResult, TErrorResult>> FullFlatMap<TResult, TErrorResult, T, TError>(
Result<T, TError> result, Func<T, Task<Result<TResult, TErrorResult>>> flatMapSelector,
Func<TError, TErrorResult> errorSelector) {
if (result.HasValue) return await flatMapSelector(result.Value).ConfigureAwait(false);
if (result.Either.HasValue) return await flatMapSelector(result.Either.Value).ConfigureAwait(false);

return errorSelector != null
? errorSelector(result.Error)
? errorSelector(result.Either.Error)
: throw new ArgumentNullException(nameof(errorSelector));
}

Expand All @@ -278,20 +278,17 @@ Func<TError, TErrorResult> errorSelector
) => (await source.ConfigureAwait(false)).FullMap(selector, errorSelector);

[Pure]
internal static async Task<bool> HasError<T, TError>(Task<Result<T, TError>> result) =>
(await result.ConfigureAwait(false)).HasError;
internal static async Task<Either<T, TError>> Either<T, TError>(Task<Result<T, TError>> result) =>
(await result.ConfigureAwait(false)).Either;

[Pure]
internal static async Task<bool> HasValue<T, TError>(Task<Result<T, TError>> result) =>
(await result.ConfigureAwait(false)).HasValue;

[Pure]
internal static async Task<Result<T, TError>> IsErrorWhen<T, TError>(Task<Result<T, TError>> source,
Func<T, Task<bool>> predicate,
Func<Maybe<T>, TError> errorSelector) {
var result = await source.ConfigureAwait(false);
if (result.HasError) return result.Error;
return await predicate(result.Value) ? (Result<T, TError>) errorSelector(result.Value) : result.Value;
if (result.Either.HasError) return result.Either.Error;
return await predicate(result.Either.Value) ? (Result<T, TError>) errorSelector(result.Either.Value) : result.Either.Value;
}

[Pure]
Expand Down Expand Up @@ -359,18 +356,18 @@ internal static async Task<Result<TResult, TError>> Map<T, TResult, TError>(Task
internal static async Task<Result<TResult, TError>> Map<T, TResult, TError>(Task<Result<T, TError>> source,
Func<T, Task<TResult>> selector) {
var result = await source.ConfigureAwait(false);
if (result.HasError) return result.Error;
if (selector != null) return await selector(result.Value);
if (result.Either.HasError) return result.Either.Error;
if (selector != null) return await selector(result.Either.Value);
throw new ArgumentNullException(nameof(selector));
}

[Pure]
internal static async Task<Result<T, TResult>> MapError<T, TResult, TError>(Task<Result<T, TError>> source,
Func<TError, Task<TResult>> selector) {
var result = await source.ConfigureAwait(false);
if (result.HasValue) return result.Value;
if (result.Either.HasValue) return result.Either.Value;

if (selector != null) return await selector(result.Error);
if (selector != null) return await selector(result.Either.Error);
throw new ArgumentNullException(nameof(selector));
}

Expand Down
4 changes: 2 additions & 2 deletions src/Lemonad.ErrorHandling/Maybe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ namespace Lemonad.ErrorHandling {
internal T Value { get; }

private Maybe(Result<T, Unit> result) {
HasValue = result.HasValue;
Value = result.Value;
HasValue = result.Either.HasValue;
Value = result.Either.Value;
_result = result;
}

Expand Down
Loading

0 comments on commit 67da7e7

Please sign in to comment.