From b645fe8c30e505411a655e0e3fa2d19ab3ae6898 Mon Sep 17 00:00:00 2001 From: Julian Thurner Date: Tue, 13 May 2025 15:09:05 +0200 Subject: [PATCH] Refactored monad chaining methods --- src/DotNext/Optional.cs | 114 ++++++++++ src/DotNext/Result.cs | 251 ++++++++++++++++++++++ src/DotNext/Threading/Tasks/Conversion.cs | 50 +++++ 3 files changed, 415 insertions(+) diff --git a/src/DotNext/Optional.cs b/src/DotNext/Optional.cs index 25718a18a..0a4bd169d 100644 --- a/src/DotNext/Optional.cs +++ b/src/DotNext/Optional.cs @@ -8,6 +8,7 @@ namespace DotNext; +using System.Threading.Tasks; using Runtime.CompilerServices; using Intrinsics = Runtime.Intrinsics; @@ -58,6 +59,18 @@ public static async Task Flatten(this Task> task) public static async Task> Convert(this Task> task, Converter converter) => (await task.ConfigureAwait(false)).Convert(converter); + /// + /// If a value is present, apply the provided mapping function to it, and if the result is + /// non-null, return an Optional describing the result. Otherwise returns . + /// + /// The type of stored in the Optional container. + /// The type of the result of the mapping function. + /// The task containing Optional value. + /// A mapping function to be applied to the value, if present. + /// An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise . + public static async Task> Convert(this Task> task, Converter> converter) + => (await task.ConfigureAwait(false)).Convert(converter); + /// /// If a value is present, apply the provided mapping function to it, and if the result is /// non-null, return an Optional describing the result. Otherwise, returns . @@ -79,6 +92,66 @@ public static async Task> Convert(this Task.None; } + /// + /// If a value is present, apply the provided mapping function to it, and if the result is + /// non-null, return an Optional describing the result. Otherwise returns . + /// + /// The type of stored in the Optional container. + /// The type of the result of the mapping function. + /// The task containing Optional value. + /// A mapping function to be applied to the value, if present. + /// The token that can be used to cancel the operation. + /// An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise . + public static async Task> Convert(this Task> task, + Func>> converter, CancellationToken token = default) + { + var optional = await task.ConfigureAwait(false); + return optional.HasValue + ? await converter.Invoke(optional.ValueOrDefault, token).ConfigureAwait(false) + : optional.IsNull && Intrinsics.IsNullable() + ? new(default) + : Optional.None; + } + + /// + /// Creates from instance. + /// + /// The optional value. + /// The converted optional value. + public static Result ToResult(this in Optional optional) + => Result.FromOptional(optional); + + /// + /// Creates from instance. + /// + /// The task containing Optional value. + /// The converted optional value. + public static async Task> ToResult(this Task> task) + => Result.FromOptional(await task.ConfigureAwait(false)); + + /// + /// Creates from instance. + /// + /// The optional value. + /// The error code to apply if the value is not present. + /// The converted optional value. + public static Result ToResult(this in Optional optional, TError error) + where TError : struct, Enum + => optional.HasValue ? new(optional.Value) : new(error); + + /// + /// Creates from instance. + /// + /// The task containing Optional value. + /// The error code to apply if the value is not present. + /// The converted optional value. + public static async Task> ToResult(this Task> task, TError error) + where TError : struct, Enum + { + var optional = await task.ConfigureAwait(false); + return optional.HasValue ? new(optional.Value) : new(error); + } + /// /// If a value is present, returns the value, otherwise throw exception. /// @@ -609,6 +682,47 @@ public Optional Convert(Converter> mapper public unsafe Optional Convert(delegate*> mapper) => ConvertOptional>>(mapper); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private async Task> ConvertTask(Func> converter, CancellationToken token = default) + => HasValue ? await converter.Invoke(value, token).ConfigureAwait(false) : Optional.None; + + /// + /// If a value is present, apply the provided mapping function to it, and if the result is + /// non-null, return an Optional describing the result. Otherwise, returns . + /// + /// The type of the result of the mapping function. + /// A mapping function to be applied to the value, if present. + /// The token that can be used to cancel the operation. + /// An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise . + public Task> Convert(Func> mapper, CancellationToken token = default) + => ConvertTask(mapper, token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Task> ConvertOptionalTask(TConverter converter) + where TConverter : struct, ISupplier>> + => HasValue ? converter.Invoke(value) : Task.FromResult(Optional.None); + + /// + /// If a value is present, apply the provided mapping function to it, and if the result is + /// non-null, return an Optional describing the result. Otherwise, returns . + /// + /// The type of the result of the mapping function. + /// A mapping function to be applied to the value, if present. + /// An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise . + public Task> Convert(Converter>> mapper) + => ConvertOptionalTask>>>(mapper); + + /// + /// If a value is present, apply the provided mapping function to it, and if the result is + /// non-null, return an Optional describing the result. Otherwise returns . + /// + /// The type of the result of the mapping function. + /// A mapping function to be applied to the value, if present. + /// An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise . + [CLSCompliant(false)] + public unsafe Task> Convert(delegate*>> mapper) + => ConvertOptionalTask>>>(mapper); + /// Func IFunctional>.ToDelegate() => Func.Constant(kind is NotEmptyValue ? value : null); diff --git a/src/DotNext/Result.cs b/src/DotNext/Result.cs index 6985974d1..d0a0d9599 100644 --- a/src/DotNext/Result.cs +++ b/src/DotNext/Result.cs @@ -7,6 +7,8 @@ namespace DotNext; +using System; +using DotNext.Threading.Tasks; using Runtime.CompilerServices; using Intrinsics = Runtime.Intrinsics; @@ -75,6 +77,98 @@ public static class Result /// The exception to be placed to the container. /// The exception encapsulated by . public static Result FromException(Exception e) => new(e); + + /// + /// Creates a new instance of from the specified exception. + /// + /// The type of the value. + /// The type of the error code. Default value must represent the successful result. + /// The error to be placed to the container. + /// The exception encapsulated by . + public static Result FromError(TError e) + where TError : struct, Enum + => new(e); + + /// + /// If successful result is present, apply the provided mapping function hiding any exception + /// caused by the converter. + /// + /// The task containing Result value. + /// A mapping function to be applied to the value, if present. + /// The type of the value. + /// The type of the result of the mapping function. + /// The conversion result. + public static AwaitableResult Convert(this AwaitableResult task, Converter converter) + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + var conversionResult = result.Convert(converter); + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } + + return ConvertInternal().SuspendException(); + } + + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the exception. + /// + /// The task containing Result value. + /// A mapping function to be applied to the value, if present. + /// The type of the value. + /// The type of the result of the mapping function. + /// The conversion result. + public static AwaitableResult Convert(this AwaitableResult task, Converter> converter) + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + var conversionResult = result.Convert(converter); + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } + + return ConvertInternal().SuspendException(); + } + + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the exception. + /// + /// The task containing Result value. + /// A mapping function to be applied to the value, if present. + /// The token that can be used to cancel the operation. + /// The type of the value. + /// The type of the result of the mapping function. + /// The conversion result. + public static AwaitableResult Convert(this AwaitableResult task, + Func> converter, CancellationToken token = default) + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + return result.IsSuccessful + ? await converter.Invoke(result.ValueOrDefault!, token).ConfigureAwait(false) + : throw result.Error; + } + + return ConvertInternal().SuspendException(); + } + + /// + /// Converts the awaitable Result into a task holding . + /// + /// A task holding an Option monad representing value in this monad. + public static async Task> TryGet(this AwaitableResult awaitableResult) + => (await awaitableResult.ConfigureAwait(false)).TryGet(); + + /// + /// Converts the awaitable Result into a task holding . + /// + /// A task holding an Option monad representing value in this monad. + public static async Task> TryGet(this AwaitableResult awaitableResult) + where TError: struct, Enum + => (await awaitableResult.ConfigureAwait(false)).TryGet(); } /// @@ -202,6 +296,77 @@ private Result Convert(TConverter converter) return result; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Result ConvertResult(TConverter converter) + where TConverter : struct, ISupplier> + { + Result result; + if (exception is null) + { + try + { + result = converter.Invoke(value); + } + catch (Exception e) + { + result = new(e); + } + } + else + { + result = new(exception); + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private AwaitableResult ConvertTask(Func> converter, CancellationToken token = default) + { + AwaitableResult result; + if (exception is null) + { + try + { + result = converter.Invoke(value, token).SuspendException(); + } + catch (Exception e) + { + result = new(Task.FromException(e)); + } + } + else + { + result = new(Task.FromException(exception.SourceException)); + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private AwaitableResult ConvertAwaitableResult(TConverter converter) + where TConverter : struct, ISupplier> + { + AwaitableResult result; + if (exception is null) + { + var valueCopy = value; + async Task GetConversionResult() + { + var conversionResult = await converter.Invoke(valueCopy).ConfigureAwait(false); + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } + + result = new(GetConversionResult()); + } + else + { + result = new(Task.FromException(exception.SourceException)); + } + + return result; + } + /// /// If the successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -212,6 +377,37 @@ private Result Convert(TConverter converter) public Result Convert(Converter converter) => Convert>(converter); + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the exception. + /// + /// A mapping function to be applied to the value, if present. + /// The type of the result of the mapping function. + /// The conversion result. + public Result Convert(Converter> converter) + => ConvertResult>>(converter); + + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the exception. + /// + /// A mapping function to be applied to the value, if present. + /// The type of the result of the mapping function. + /// The token that can be used to cancel the operation. + /// The conversion result. + public AwaitableResult Convert(Func> converter, CancellationToken token = default) + => ConvertTask(converter, token); + + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the exception. + /// + /// A mapping function to be applied to the value, if present. + /// The type of the result of the mapping function. + /// The conversion result. + public AwaitableResult Convert(Converter> converter) + => ConvertAwaitableResult>>(converter); + /// /// If the successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -223,6 +419,28 @@ public Result Convert(Converter converter) public unsafe Result Convert(delegate* converter) => Convert>(converter); + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the exception. + /// + /// A mapping function to be applied to the value, if present. + /// The type of the result of the mapping function. + /// The conversion result. + [CLSCompliant(false)] + public unsafe Result Convert(delegate*> converter) + => ConvertResult>>(converter); + + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the exception. + /// + /// A mapping function to be applied to the value, if present. + /// The type of the result of the mapping function. + /// The conversion result. + [CLSCompliant(false)] + public unsafe AwaitableResult Convert(delegate*> converter) + => ConvertAwaitableResult>>(converter); + /// /// Attempts to extract value from the container if it is present. /// @@ -323,6 +541,13 @@ public ValueTask AsTask() { } error => ValueTask.FromException(error), }; + /// + /// Converts this result to . + /// + /// The awaitable Result representing the result. + public AwaitableResult ToAwaitable() + => IsSuccessful ? new(Task.FromResult(value)) : new(Task.FromException(Error)); + /// /// Converts the result to . /// @@ -534,6 +759,11 @@ private Result Convert(TConverter converte where TConverter : struct, ISupplier => IsSuccessful ? new(converter.Invoke(value)) : new(Error); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Result ConvertResult(TConverter converter) + where TConverter : struct, ISupplier> + => IsSuccessful ? converter.Invoke(value) : new(Error); + /// /// If the successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -544,6 +774,16 @@ private Result Convert(TConverter converte public Result Convert(Converter converter) => Convert>(converter); + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the error. + /// + /// A mapping function to be applied to the value, if present. + /// The type of the result of the mapping function. + /// The conversion result. + public Result Convert(Converter> converter) + => ConvertResult>>(converter); + /// /// If the successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -555,6 +795,17 @@ public Result Convert(Converter converter) public unsafe Result Convert(delegate* converter) => Convert>(converter); + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the error. + /// + /// A mapping function to be applied to the value, if present. + /// The type of the result of the mapping function. + /// The conversion result. + [CLSCompliant(false)] + public unsafe Result Convert(delegate*> converter) + => ConvertResult>>(converter); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private T OrInvoke(TSupplier defaultFunc) where TSupplier : struct, ISupplier diff --git a/src/DotNext/Threading/Tasks/Conversion.cs b/src/DotNext/Threading/Tasks/Conversion.cs index 80be9e90c..f6f8cbbf1 100644 --- a/src/DotNext/Threading/Tasks/Conversion.cs +++ b/src/DotNext/Threading/Tasks/Conversion.cs @@ -3,6 +3,7 @@ namespace DotNext.Threading.Tasks; +using System.Threading.Tasks; using Runtime.CompilerServices; /// @@ -20,6 +21,16 @@ public static class Conversion public static Task Convert(this Task task) where TInput : TOutput => task.Convert(Converter.Identity()); + /// + /// Converts one type of into another. + /// + /// The source Result type. + /// The target Result type. + /// The awaitable Result to convert. + /// The converted task. + public static AwaitableResult Convert(this AwaitableResult awaitableResult) + where TInput : TOutput => awaitableResult.Convert(Converter.Identity()); + /// /// Converts one type of task into another. /// @@ -31,6 +42,25 @@ public static Task Convert(this Task task) public static async Task Convert(this Task task, Converter converter) => converter(await task.ConfigureAwait(false)); + /// + /// Converts one type of into another. + /// + /// The source Result type. + /// The target Result type. + /// The awaitable Result to convert. + /// Non-blocking conversion function. + /// The converted task. + public static AwaitableResult Convert(this AwaitableResult awaitableResult, Converter converter) + { + async Task ConvertInternal() + { + var result = await awaitableResult.ConfigureAwait(false); + return result.IsSuccessful ? converter(result.Value) : throw result.Error; + } + + return ConvertInternal().SuspendException(); + } + /// /// Converts value type into nullable value type. /// @@ -53,6 +83,26 @@ public static async Task Convert(this Task tas public static async Task Convert(this Task task, Converter> converter) => await converter(await task.ConfigureAwait(false)).ConfigureAwait(false); + /// + /// Converts one type of into another. + /// + /// The source Result type. + /// The target Result type. + /// The awaitable Result to convert. + /// Asynchronous conversion function. + /// The converted task. + public static AwaitableResult Convert(this AwaitableResult awaitableResult, Converter> converter) + { + async Task ConvertInternal() + { + var result = await awaitableResult.ConfigureAwait(false); + var conversionResult = result.IsSuccessful ? await converter(result.Value).ConfigureAwait(false) : throw result.Error; + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } + + return ConvertInternal().SuspendException(); + } + /// /// Allows to convert of unknown result type into dynamically /// typed task which result can be obtained as