Last active
June 2, 2020 09:26
-
-
Save dzmitry-lahoda/78bd89c3aa804aa0467dcba0a2e1defd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#nullable enable | |
using System; | |
using System.Fun; | |
using System.Net; | |
using System.Threading.Tasks; | |
namespace Microsoft.AspNetCore.Mvc | |
{ | |
public interface MvcError : IResult | |
{ | |
bool IsOk => false; | |
bool IsError => true; | |
ProblemDetails Value { get; } | |
} | |
/// <summary> | |
/// Return result for web. | |
/// </summary> | |
public abstract class MvcResult<T> | |
where T : notnull | |
{ | |
public static MvcResult<T> Of(ProblemDetails value) => new Error(value); | |
public static MvcResult<T> Of(T value) => new Ok(value); | |
public abstract bool IsOk { get; } | |
public abstract bool IsError { get; } | |
public static implicit operator MvcResult<T>(T value) => new Ok(value); | |
public static implicit operator MvcResult<T>(ProblemDetails value) => new Error(value); | |
private MvcResult() { } | |
public sealed class Ok : MvcResult<T>, Ok<T> | |
{ | |
public Ok(T value) => Value = value ?? throw new ArgumentNullException("value"); | |
public T Value { get; } | |
public static implicit operator Ok(T value) => new Ok(value); | |
public static implicit operator T(Ok result) => result.Value; | |
public override bool IsOk => true; | |
public override bool IsError => false; | |
} | |
public sealed class Error : MvcResult<T>, MvcError | |
{ | |
public Error(ProblemDetails value) => Value = value ?? throw new ArgumentNullException("value"); | |
public ProblemDetails Value { get; } | |
public static implicit operator Error(ProblemDetails value) => new Error(value); | |
public static implicit operator ProblemDetails(Error result) => result.Value; | |
public override bool IsOk => false; | |
public override bool IsError => true; | |
public ObjectResult Problem() => | |
new ObjectResult(Value) { StatusCode = Value.Status }; | |
} | |
public ObjectResult Result => | |
(this) switch | |
{ | |
Ok o => new ObjectResult(o), | |
Error e => e.Problem() | |
}; | |
public MvcResult<TOut> Switch<TOut>(Func<T, TOut> okCase, Func<ProblemDetails, ProblemDetails> errorCase) => | |
this switch | |
{ | |
Ok ok => okCase(ok.Value), | |
Error fail => errorCase(fail.Value) | |
}; | |
public MvcResult<TOut> OkCase<TOut>(Func<T, TOut> okCase) => | |
this switch | |
{ | |
Ok ok => okCase(ok.Value), | |
Error fail => fail.Value | |
}; | |
public async Task<MvcResult<TOut>> SwitchAsync<TOut>(Func<T, Task<TOut>> okCase, Func<ProblemDetails, ProblemDetails> errorCase) => | |
this switch | |
{ | |
Ok ok => await okCase(ok.Value), | |
Error fail => errorCase(fail.Value) | |
}; | |
public async Task<MvcResult<TOut>> OkCaseAsync<TOut>(Func<T, Task<TOut>> okCase) => | |
this switch | |
{ | |
Ok ok => await okCase(ok.Value), | |
Error fail => fail.Value | |
}; | |
} | |
} | |
using System; | |
using System.Collections.Generic; | |
using System.Fun; | |
using System.Linq; | |
using System.Threading.Tasks; | |
namespace Microsoft.AspNetCore.Mvc | |
{ | |
public static class ControllerExtensions | |
{ | |
public static ObjectResult Problem(this ControllerBase self, ProblemDetails data) => | |
self.Problem(data); | |
// of result | |
public static MvcResult<TOut> Switch<T, TOut>(this Result<T> self, Func<T, TOut> okCase, Func<Exception, ProblemDetails> errorCase) => | |
self switch | |
{ | |
Result<T>.Ok ok => okCase(ok.Value), | |
Result<T>.Error fail => errorCase(fail.Value) | |
}; | |
// to result | |
public static Result<TOut> Switch<T, TOut>(this MvcResult<T> self, Func<T, TOut> okCase, Func<ProblemDetails, Exception> errorCase) => | |
self switch | |
{ | |
MvcResult<T>.Ok ok => okCase(ok.Value), | |
MvcResult<T>.Error fail => errorCase(fail.Value) | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Choose Optional(Opt) and write combinators for Opt<T, Exception> Opt<T,Task<TOther>, Opt<T, ProblemDetails> | |
// Unions are good per ce on C#8 recursive pattern matching and will be best on 9 with generator and #r nuget | |
// Does not go into forest of Free Monads, Tagles Final, Interpreters | |
// | |
// Dictionary<string, Foo> stats = | |
// await bar.SelectMany(x => x.Bazz) | |
// .Distinct() | |
// .Batch(13) | |
// .SelectTo(fizz, workWork) | |
// .SelectManyAsync(x => x) | |
// .ToDictionaryAsync(x => x.A, _ => _); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Runtime.CompilerServices; | |
namespace System.Linq | |
{ | |
internal static class Extensions | |
{ | |
public static Func<T2, TResult> Partial<T1, T2, TResult>(T1 first, Func<T1, T2, TResult> map) | |
{ | |
Func<T2, TResult> func = t2 => map(first, t2); | |
return func; | |
} | |
/// <summary> | |
/// Forward pipe operator (`|>` in F#) but with side effect propagating the original `x` value | |
/// </summary> | |
public static T Tap<T>(this T x, Action<T> effect) | |
{ | |
effect(x); | |
return x; | |
} | |
/// <summary> | |
/// Forward pipe operator (`|>` in F#) but with side effect propagating the original `x` value and the state object | |
/// </summary> | |
public static T Tap<T, S>(this T x, S state, Action<T, S> effect) | |
{ | |
effect(x, state); | |
return x; | |
} | |
public static IEnumerable<TResult> SelectTo<T1, T2, TResult>(this IEnumerable<T2> self, T1 first, Func<T1, T2, TResult> selector) => | |
self.Select(_ => _.To(first, selector)); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static TResult To<T1, T2, T3, TResult>(this (T2 second, T3 third) self, T1 first, Func<T1, T2, T3, TResult> map) => | |
map(first, self.second, self.third); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static TResult To<T1, T2, T3, TResult>(this T3 self, T1 first, T2 second, Func<T1, T2, T3, TResult> map) => | |
map(first, second, self); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static TResult To<T1, T2, T3, TResult>(this T3 self, (T1 first, T2 second) part, Func<T1, T2, T3, TResult> map) => | |
map(part.first, part.second, self); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static TResult To<T1, T2, T3, TResult>(this T3 self, (T1 first, T2 second) part, Func<(T1, T2), T3, TResult> map) => | |
map(part, self); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static TResult To<T1, T2, T3, T4, TResult>(this T4 self, T1 first, T2 second, T3 third, Func<T1, T2, T3, T4, TResult> map) => | |
map(first, second, third, self); | |
/// <summary> | |
/// Pipe operator for funcitonal chaining. | |
/// </summary> | |
public static R To<T, R>(this T self, Func<T, R> map) => map(self); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static TResult To<T1, T2, TResult>(this T2 self, T1 first, Func<T1, T2, TResult> map) => | |
map(first, self); | |
// similar to `as..select` in Gremlin or to `let` in C# LINQ | |
public static IEnumerable<T> SelectLet<T, V>(this IEnumerable<T> self, out V var, Func<IEnumerable<T>, V> sideEffect) | |
{ | |
var = self.To(sideEffect); | |
return self; | |
} | |
public static IEnumerable<T> SelectLet<T>(this IEnumerable<T> self, out IEnumerable<T> var) | |
{ | |
var = self; | |
return self; | |
} | |
///<summary> | |
/// If null, than throws. | |
/// If not null than passes object as it as not nullable. | |
///</summary> | |
public static T NotNull<T>(this T self, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "") | |
where T : class | |
=> | |
self != null ? self : throw new ArgumentNullException(memberName); | |
public static IEnumerable<T1> SelectFirst<T1, T2>(this IEnumerable<ValueTuple<T1, T2>> self) => | |
self.Select(x => x.Item1); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#nullable enable | |
using System.Threading.Tasks; | |
namespace System.Fun | |
{ | |
public interface IResult | |
{ | |
bool IsOk { get; } | |
bool IsError { get; } | |
} | |
public interface IOk : IResult | |
{ | |
bool IsOk => true; | |
bool IsError => false; | |
} | |
public interface Ok<T> : IOk | |
{ | |
T Value { get; } | |
} | |
public interface Error : IResult | |
{ | |
bool IsOk => false; | |
bool IsError => true; | |
Exception Value { get; } | |
} | |
/// <summary> | |
/// Optional result. Must check for null. | |
/// This result works with C# 8 pattern matching. | |
/// </summary> | |
public abstract class OptionalResult<T> : IResult | |
where T : class | |
{ | |
public static OptionalResult<T?> Of(Exception value) => new Error(value); | |
public static OptionalResult<T?> Of(T? value) => new Ok(value); | |
public abstract bool IsOk { get; } | |
public abstract bool IsError { get; } | |
private OptionalResult() { } | |
public static implicit operator OptionalResult<T>(T value) => new Ok(value); | |
public static implicit operator OptionalResult<T>(Exception value) => new Error(value); | |
public sealed class Ok : OptionalResult<T?>, Ok<T?> | |
{ | |
public Ok(T? value) => Value = value; | |
public T? Value { get; } | |
public static implicit operator Ok(T value) => new Ok(value); | |
public static implicit operator T(Ok result) => result.Value; | |
public override bool IsOk => true; | |
public override bool IsError => false; | |
} | |
public sealed class Error : OptionalResult<T>, Fun.Error | |
{ | |
public Error(Exception value) => Value = value ?? throw new ArgumentNullException("value"); | |
public Exception Value { get; } | |
public static implicit operator Error(Exception value) => new Error(value); | |
public static implicit operator Exception(Error result) => result.Value; | |
public override bool IsOk => false; | |
public override bool IsError => true; | |
public Exception Throw() => throw Value; | |
} | |
} | |
/// <summary> | |
/// Non optional result. | |
/// This result works with C# 8 pattern matching. | |
/// </summary> | |
public abstract class Result<T> : IResult | |
where T : notnull | |
{ | |
public static Result<T> Of(Exception value) => new Error(value); | |
public static Result<T> Of(T value) => new Ok(value); | |
public abstract bool IsOk { get; } | |
public abstract bool IsError { get; } | |
private Result() { } | |
public static implicit operator Result<T>(T value) => new Ok(value); | |
public static implicit operator Result<T>(Exception value) => new Error(value); | |
public sealed class Ok : Result<T>, Ok<T> | |
{ | |
public Ok(T value) => Value = value ?? throw new ArgumentNullException("value"); | |
public T Value { get; } | |
public static implicit operator Ok(T value) => new Ok(value); | |
public static implicit operator T(Ok result) => result.Value; | |
public override bool IsOk => true; | |
public override bool IsError => false; | |
} | |
public sealed class Error : Result<T>, Fun.Error | |
{ | |
public Error(Exception value) => Value = value ?? throw new ArgumentNullException("value"); | |
public Exception Value { get; } | |
public static implicit operator Error(Exception value) => new Error(value); | |
public static implicit operator Exception(Error result) => result.Value; | |
public override bool IsOk => false; | |
public override bool IsError => true; | |
public Exception Throw() => throw Value; | |
} | |
public Result<TOut> Switch<TOut>(Func<T, TOut> okCase, Func<Exception, Exception> errorCase) => | |
this switch | |
{ | |
Ok ok => okCase(ok.Value), | |
Error fail => errorCase(fail.Value) | |
}; | |
public Result<TOut> OkCase<TOut>(Func<T, TOut> okCase) => | |
this switch | |
{ | |
Ok ok => okCase(ok.Value), | |
Error fail => fail.Value | |
}; | |
public async Task<Result<TOut>> SwitchAsync<TOut>(Func<T, Task<TOut>> okCase, Func<Exception, Exception> errorCase) => | |
this switch | |
{ | |
Ok ok => await okCase(ok.Value), | |
Error fail => errorCase(fail.Value) | |
}; | |
public async Task<Result<TOut>> OkCaseAsync<TOut>(Func<T, Task<TOut>> okCase) => | |
this switch | |
{ | |
Ok ok => await okCase(ok.Value), | |
Error fail => fail.Value | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Fun; | |
using System.Runtime.ExceptionServices; | |
namespace System.Linq | |
{ | |
public static class ResultExtensions | |
{ | |
public static IEnumerable<Result<TResult>> TrySelect<TIn, TResult>(this IEnumerable<TIn> self, Func<TIn, TResult> selector) | |
{ | |
foreach (var item in self) | |
{ | |
Result<TResult> result; | |
try | |
{ | |
result = selector(item); | |
} | |
catch (Exception ex) | |
{ | |
result = ExceptionDispatchInfo.Capture(ex).SourceException; | |
} | |
yield return result; | |
} | |
} | |
public static IEnumerable<TResult> SelectOk<TResult>(this IEnumerable<Result<TResult>> self) => | |
self.Where(x => x.IsOk).Cast<Result<TResult>.Ok>().Select(x=> x.Value); | |
public static void SelectThrow<TResult>(this IEnumerable<Result<TResult>> self) | |
{ | |
var errors = self.Where(x => x.IsError).Cast<Result<TResult>.Error>().Select(x => x.Value); | |
if (errors.Any()) | |
throw new AggregateException(errors); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Linq; | |
using System.Runtime.CompilerServices; | |
namespace System.Threading.Tasks | |
{ | |
internal static class Extensions | |
{ | |
public static async Task<TResult> SelectAsync<T, TResult>(this Task<T> self, Func<T, TResult> map) => | |
map(await self.IgnoreContext()); | |
public static async Task<TResult> ToAsync<T, TResult>(this Task<T> self, Func<T, Task<TResult>> map) => | |
await map(await self.IgnoreContext()).IgnoreContext(); | |
public static async Task<TResult> ToAsync<T, TResult>(this Task<T> self, Func<T, TResult> map) => | |
map(await self.IgnoreContext()); | |
public static async Task<TResult> To<T, TResult>(this Task<T> self, Func<T, TResult> map) | |
{ | |
var result = await self; | |
return map(result); | |
} | |
public static async ValueTask<TResult> SelectAsync<T, TResult>(this ValueTask<T> x, Func<T, TResult> map) => | |
map(await x.IgnoreContext()); | |
/// <summary> | |
/// Returns completed task if current is null. | |
/// </summary> | |
public static Task IfNullCompleted(this Task self) => self ?? Task.CompletedTask; | |
public static async ValueTask<TResult> ToAsync<T, TResult>(this ValueTask<T> x, Func<T, Task<TResult>> map) => | |
await map(await x.IgnoreContext()).IgnoreContext(); | |
public static async Task ToAsync<T>(this Task<T> x, Func<T, Task> map) => | |
await map(await x.IgnoreContext()).IgnoreContext(); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static ConfiguredTaskAwaitable<T> IgnoreContext<T>(this Task<T> task) => | |
task.ConfigureAwait(false); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static ConfiguredTaskAwaitable IgnoreContext(this Task task) => | |
task.ConfigureAwait(false); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static ConfiguredValueTaskAwaitable<T> IgnoreContext<T>(this ValueTask<T> task) => | |
task.ConfigureAwait(false); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static ConfiguredValueTaskAwaitable IgnoreContext(this ValueTask task) => | |
task.ConfigureAwait(false); | |
// we do not have type classes, so just produce combination for each container(List/Enumerable or Task/Value task for our cases) | |
// once written - gets reused | |
public static async Task<IEnumerable<TResult>> SelectAsync<TOut, TResult>(this Task<List<TOut>> self, Func<TOut, TResult> selector) | |
{ | |
var items = await self; | |
return items.Select(selector); | |
} | |
public static Task<IEnumerable<TResult>> SelectToAsync<T1, T2, TResult>(this Task<IEnumerable<T2>> self, T1 first, Func<T1, T2, TResult> selector) => | |
self.SelectAsync(_ => _.To(first, selector)); | |
public static Task<IEnumerable<TResult>> SelectToAsync<T1, T2, TResult>(this Task<List<T2>> self, T1 first, Func<T1, T2, TResult> selector) => | |
self.SelectAsync(_ => _.To(first, selector)); | |
public static Task<IEnumerable<TResult>> SelectToAsync<T1, T2, T3, TResult>(this Task<List<T3>> self, T1 first, T2 second, Func<T1, T2, T3, TResult> selector) => | |
self.SelectAsync(_ => _.To(first, second, selector)); | |
public static Task<IEnumerable<TResult>> SelectToAsync<T1, T2, T3, TResult>(this Task<IEnumerable<T3>> self, T1 first, T2 second, Func<T1, T2, T3, TResult> selector) => | |
self.SelectAsync(_ => _.To(first, second, selector)); | |
// C# 9 allows to generate these repated items | |
public static async Task<IEnumerable<TResult>> SelectAsync<TOut, TResult>(this Task<HashSet<TOut>> self, Func<TOut, TResult> selector) | |
{ | |
var items = await self; | |
return items.Select(selector); | |
} | |
public static async Task<IEnumerable<TResult>> SelectAsync<TOut, TResult>(this Task<TOut[]> self, Func<TOut, TResult> selector) | |
{ | |
var items = await self; | |
return items.Select(selector); | |
} | |
public static async Task<IEnumerable<TResult>> SelectAsync<TOut, TResult>(this Task<IList<TOut>> self, Func<TOut, TResult> selector) | |
{ | |
var items = await self; | |
return items.Select(selector); | |
} | |
public static async Task<IEnumerable<TResult>> SelectAsync<TOut, TResult>(this Task<IEnumerable<TOut>> self, Func<TOut, TResult> selector) | |
{ | |
var items = await self; | |
return items.Select(selector); | |
} | |
public static async ValueTask<IEnumerable<TResult>> SelectAsync<TOut, TResult>(this ValueTask<IEnumerable<TOut>> self, Func<TOut, TResult> selector) | |
{ | |
var items = await self; | |
return items.Select(selector); | |
} | |
public static async Task<IEnumerable<TResult>> SelectManyAsync<T, TResult>(this IEnumerable<Task<T>> self, Func<T, IEnumerable<TResult>> map) | |
{ | |
var results = new List<TResult>(); | |
foreach(var item in self) | |
{ | |
var result = await item; | |
results.AddRange(map(result)); | |
} | |
return results; | |
} | |
public static async Task<Dictionary<TKey,TValue>> ToDictionaryAsync<T, TKey, TValue>(this Task<IEnumerable<T>> self, Func<T, TKey> key, Func<T, TValue> value) | |
{ | |
var result = await self; | |
return result.ToDictionary(key, value); | |
} | |
public static async ValueTask<IEnumerable<TResult>> SelectManyAsync<T, TResult>(this IEnumerable<ValueTask<T>> self, Func<T, IEnumerable<TResult>> map) | |
{ | |
var results = new List<TResult>(); | |
foreach (var item in self) | |
{ | |
var result = await item; | |
results.AddRange(map(result)); | |
} | |
return results; | |
} | |
public static async ValueTask<Dictionary<TKey, TValue>> ToDictionaryAsync<T, TKey, TValue>(this ValueTask<IEnumerable<T>> self, Func<T, TKey> key, Func<T, TValue> value) | |
{ | |
var result = await self; | |
return result.ToDictionary(key, value); | |
} | |
public static async Task<HashSet<T>> ToHashSetAsync<T>(this Task<IEnumerable<T>> self) | |
{ | |
var result = await self; | |
return result.ToHashSet(); | |
} | |
// etc... | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment