From 7d3b2ae34d620c1523a5326d2613753bcce74316 Mon Sep 17 00:00:00 2001 From: Julian Thurner Date: Tue, 4 Feb 2025 12:52:44 +0100 Subject: [PATCH 1/4] Extensions for monad chaining --- src/DotNext/Optional.cs | 89 +++++++++++++++ src/DotNext/Result.cs | 235 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+) diff --git a/src/DotNext/Optional.cs b/src/DotNext/Optional.cs index c6dc79320..35d4b731b 100644 --- a/src/DotNext/Optional.cs +++ b/src/DotNext/Optional.cs @@ -48,6 +48,69 @@ public static class Optional 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 . + /// + /// 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 (await task.ConfigureAwait(false)).Convert(converter).ConfigureAwait(false); + + /// + /// 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. /// @@ -553,6 +616,11 @@ private Optional ConvertOptional(TConverter conver where TConverter : struct, ISupplier> => HasValue ? converter.Invoke(value) : Optional.None; + [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 . @@ -570,10 +638,31 @@ public Optional Convert(Converter> mapper /// 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 Optional Convert(delegate*> mapper) => ConvertOptional>>(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 c9b2839eb..19b9fb7be 100644 --- a/src/DotNext/Result.cs +++ b/src/DotNext/Result.cs @@ -75,6 +75,99 @@ public static class Result /// The exception to be placed to the container. /// The exception encapsulated by . public static Result FromException(Exception e) => 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 async Task> Convert(this Task> task, Converter converter) + => (await task.ConfigureAwait(false)).Convert(converter); + + /// + /// 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 async Task> Convert(this Task> task, Converter> converter) + => (await task.ConfigureAwait(false)).Convert(converter); + + /// + /// 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 async Task> Convert(this Task> task, Converter>> converter) + => await (await task.ConfigureAwait(false)).Convert(converter).ConfigureAwait(false); + + /// + /// 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 error code. Default value must represent the successful result. + /// The type of the result of the mapping function. + /// The conversion result. + public static async Task> Convert(this Task> task, Converter converter) + where TError: struct, Enum + => (await task.ConfigureAwait(false)).Convert(converter); + + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the error. + /// + /// 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 error code. Default value must represent the successful result. + /// The type of the result of the mapping function. + /// The conversion result. + public static async Task> Convert(this Task> task, Converter> converter) + where TError : struct, Enum + => (await task.ConfigureAwait(false)).Convert(converter); + + /// + /// If successful result is present, apply the provided mapping function. If not, + /// forward the error. + /// + /// 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 error code. Default value must represent the successful result. + /// The type of the result of the mapping function. + /// The conversion result. + public static async Task> Convert(this Task> task, Converter>> converter) + where TError : struct, Enum + => await (await task.ConfigureAwait(false)).Convert(converter).ConfigureAwait(false); + + /// + /// Converts the result into . + /// + /// Option monad representing value in this monad. + public static async Task> TryGet(this Task> task) + => (await task.ConfigureAwait(false)).TryGet(); + + /// + /// Converts the result into . + /// + /// Option monad representing value in this monad. + public static async Task> TryGet(this Task> task) + where TError : struct, Enum + => (await task.ConfigureAwait(false)).TryGet(); } /// @@ -201,6 +294,54 @@ 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 Task> ConvertResultTask(TConverter converter) + where TConverter : struct, ISupplier>> + { + Task> result; + if (exception is null) + { + try + { + result = converter.Invoke(value); + } + catch (Exception e) + { + result = Task.FromResult(new Result(e)); + } + } + else + { + result = Task.FromResult(new Result(exception)); + } + + return result; + } + /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -211,6 +352,26 @@ 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 conversion result. + public Task> Convert(Converter>> converter) + => ConvertResultTask>>>(converter); + /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -222,6 +383,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 Task> Convert(delegate*>> converter) + => ConvertResultTask>>>(converter); + /// /// Attempts to extract value from container if it is present. /// @@ -537,6 +720,16 @@ 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); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Task> ConvertResultTask(TConverter converter) + where TConverter : struct, ISupplier>> + => IsSuccessful ? converter.Invoke(value) : Task.FromResult(new Result(Error)); + /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -547,6 +740,26 @@ 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 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 Task> Convert(Converter>> converter) + => ConvertResultTask>>>(converter); + /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -558,6 +771,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 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); + + /// + /// 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 Task> Convert(delegate*>> converter) + => ConvertResultTask>>>(converter); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private T OrInvoke(TSupplier defaultFunc) where TSupplier : struct, ISupplier From d0011cdc823983e67247cf859afbf59ee3f43bdf Mon Sep 17 00:00:00 2001 From: Julian Thurner Date: Tue, 4 Feb 2025 15:25:36 +0100 Subject: [PATCH 2/4] Optional and Result Extensions for quick creation --- src/DotNext/Optional.cs | 8 ++++++++ src/DotNext/Result.cs | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/DotNext/Optional.cs b/src/DotNext/Optional.cs index 35d4b731b..c0518e4a9 100644 --- a/src/DotNext/Optional.cs +++ b/src/DotNext/Optional.cs @@ -72,6 +72,14 @@ public static async Task> Convert(this Task> Convert(this Task> task, Converter>> converter) => await (await task.ConfigureAwait(false)).Convert(converter).ConfigureAwait(false); + /// + /// Creates a new instance of from the specified value. + /// + /// The type of the value. + /// The value to be placed to the container. + /// The value encapsulated by . + public static Optional FromValue(T value) => new(value); + /// /// Creates from instance. /// diff --git a/src/DotNext/Result.cs b/src/DotNext/Result.cs index 19b9fb7be..e6526a6a2 100644 --- a/src/DotNext/Result.cs +++ b/src/DotNext/Result.cs @@ -68,6 +68,17 @@ public static class Result /// The value encapsulated by . public static Result FromValue(T value) => new(value); + /// + /// Creates a new instance of from the specified value. + /// + /// The type of the value. + /// The type of the error code. Default value must represent the successful result. + /// The value to be placed to the container. + /// The value encapsulated by . + public static Result FromValue(T value) + where TError: struct, Enum + => new(value); + /// /// Creates a new instance of from the specified exception. /// @@ -76,6 +87,17 @@ public static class Result /// 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. From f58043fbc47f47855ab772acd3157565a32eac1e Mon Sep 17 00:00:00 2001 From: Julian Thurner Date: Wed, 26 Feb 2025 12:01:05 +0100 Subject: [PATCH 3/4] Adjustments for AwaitableResult --- src/DotNext/Optional.cs | 2 +- src/DotNext/Result.cs | 246 ++++++++++++++-------- src/DotNext/Threading/Tasks/Conversion.cs | 50 +++++ 3 files changed, 213 insertions(+), 85 deletions(-) diff --git a/src/DotNext/Optional.cs b/src/DotNext/Optional.cs index c0518e4a9..98fb8b414 100644 --- a/src/DotNext/Optional.cs +++ b/src/DotNext/Optional.cs @@ -78,7 +78,7 @@ public static async Task> Convert(this TaskThe type of the value. /// The value to be placed to the container. /// The value encapsulated by . - public static Optional FromValue(T value) => new(value); + public static Optional From(T value) => new(value); /// /// Creates from instance. diff --git a/src/DotNext/Result.cs b/src/DotNext/Result.cs index e6526a6a2..ae5238826 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; @@ -68,17 +70,6 @@ public static class Result /// The value encapsulated by . public static Result FromValue(T value) => new(value); - /// - /// Creates a new instance of from the specified value. - /// - /// The type of the value. - /// The type of the error code. Default value must represent the successful result. - /// The value to be placed to the container. - /// The value encapsulated by . - public static Result FromValue(T value) - where TError: struct, Enum - => new(value); - /// /// Creates a new instance of from the specified exception. /// @@ -98,6 +89,30 @@ public static Result FromError(TError e) where TError: struct, Enum => new(e); + private static AwaitableResult TransformAwaitableResult(this AwaitableResult task, Converter, Result> converter) + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + var conversionResult = converter(result); + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } + + return ConvertInternal().SuspendException(); + } + + private static AwaitableResult TransformAwaitableResult(this AwaitableResult task, Converter, AwaitableResult> converter) + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + var conversionResult = await converter(result); + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } + + return ConvertInternal().SuspendException(); + } + /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -107,8 +122,8 @@ public static Result FromError(TError e) /// The type of the value. /// The type of the result of the mapping function. /// The conversion result. - public static async Task> Convert(this Task> task, Converter converter) - => (await task.ConfigureAwait(false)).Convert(converter); + public static AwaitableResult Convert(this AwaitableResult task, Converter converter) + => task.TransformAwaitableResult((result) => result.Convert(converter)); /// /// If successful result is present, apply the provided mapping function. If not, @@ -119,8 +134,8 @@ public static async Task> Convert(this TaskThe type of the value. /// The type of the result of the mapping function. /// The conversion result. - public static async Task> Convert(this Task> task, Converter> converter) - => (await task.ConfigureAwait(false)).Convert(converter); + public static AwaitableResult Convert(this AwaitableResult task, Converter> converter) + => task.TransformAwaitableResult((result) => result.Convert(converter)); /// /// If successful result is present, apply the provided mapping function. If not, @@ -131,50 +146,20 @@ public static async Task> Convert(this TaskThe type of the value. /// The type of the result of the mapping function. /// The conversion result. - public static async Task> Convert(this Task> task, Converter>> converter) - => await (await task.ConfigureAwait(false)).Convert(converter).ConfigureAwait(false); - - /// - /// 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 error code. Default value must represent the successful result. - /// The type of the result of the mapping function. - /// The conversion result. - public static async Task> Convert(this Task> task, Converter converter) - where TError: struct, Enum - => (await task.ConfigureAwait(false)).Convert(converter); + public static AwaitableResult Convert(this AwaitableResult task, Converter> converter) + => task.TransformAwaitableResult((result) => result.Convert(converter)); /// /// If successful result is present, apply the provided mapping function. If not, - /// forward the error. - /// - /// 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 error code. Default value must represent the successful result. - /// The type of the result of the mapping function. - /// The conversion result. - public static async Task> Convert(this Task> task, Converter> converter) - where TError : struct, Enum - => (await task.ConfigureAwait(false)).Convert(converter); - - /// - /// If successful result is present, apply the provided mapping function. If not, - /// forward the error. + /// 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 error code. Default value must represent the successful result. /// The type of the result of the mapping function. /// The conversion result. - public static async Task> Convert(this Task> task, Converter>> converter) - where TError : struct, Enum - => await (await task.ConfigureAwait(false)).Convert(converter).ConfigureAwait(false); + public static AwaitableResult Convert(this AwaitableResult task, Converter>> converter) + => task.TransformAwaitableResult((result) => result.Convert(converter)); /// /// Converts the result into . @@ -183,6 +168,13 @@ public static async Task> Convert(th public static async Task> TryGet(this Task> task) => (await task.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) + => (await awaitableResult.ConfigureAwait(false)).TryGet(); + /// /// Converts the result into . /// @@ -190,6 +182,21 @@ public static async Task> TryGet(this Task> task) public static async Task> TryGet(this Task> task) where TError : struct, Enum => (await task.ConfigureAwait(false)).TryGet(); + + /// + /// Converts this task containing a Result to . + /// + /// The completed task representing the result. + public static AwaitableResult ToAwaitable(this Task> task) + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + return result.IsSuccessful ? result.Value : throw result.Error; + } + + return ConvertInternal().SuspendException(); + } } /// @@ -341,24 +348,72 @@ private Result ConvertResult(TConverter converter) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Task> ConvertResultTask(TConverter converter) - where TConverter : struct, ISupplier>> + private AwaitableResult ConvertTask(TConverter converter) + where TConverter : struct, ISupplier> { - Task> result; + AwaitableResult result; if (exception is null) { try { - result = converter.Invoke(value); + result = converter.Invoke(value).SuspendException(); } catch (Exception e) { - result = Task.FromResult(new Result(e)); + result = new(Task.FromException(e)); } } else { - result = Task.FromResult(new Result(exception)); + result = new(Task.FromException(exception.SourceException)); + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private AwaitableResult ConvertResultTask(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; + } + + [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; @@ -391,9 +446,29 @@ public Result Convert(Converter> converter) /// A mapping function to be applied to the value, if present. /// The type of the result of the mapping function. /// The conversion result. - public Task> Convert(Converter>> converter) + public AwaitableResult Convert(Converter> converter) + => ConvertTask>>(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 AwaitableResult Convert(Converter>> converter) => ConvertResultTask>>>(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 AwaitableResult Convert(Converter> converter) + => ConvertAwaitableResult>>(converter); + /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -424,9 +499,31 @@ public unsafe Result Convert(delegate*> con /// The type of the result of the mapping function. /// The conversion result. [CLSCompliant(false)] - public unsafe Task> Convert(delegate*>> converter) + public unsafe AwaitableResult Convert(delegate*> converter) + => ConvertTask>>(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) => ConvertResultTask>>>(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 container if it is present. /// @@ -527,6 +624,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 . /// @@ -747,11 +851,6 @@ private Result ConvertResult(TConverter co where TConverter : struct, ISupplier> => IsSuccessful ? converter.Invoke(value) : new(Error); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Task> ConvertResultTask(TConverter converter) - where TConverter : struct, ISupplier>> - => IsSuccessful ? converter.Invoke(value) : Task.FromResult(new Result(Error)); - /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -772,16 +871,6 @@ public Result Convert(Converter converter) public Result Convert(Converter> converter) => ConvertResult>>(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 Task> Convert(Converter>> converter) - => ConvertResultTask>>>(converter); - /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -804,17 +893,6 @@ public unsafe Result Convert(delegate* con public unsafe Result Convert(delegate*> converter) => ConvertResult>>(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 Task> Convert(delegate*>> converter) - => ConvertResultTask>>>(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 ac0535739..9624a3f25 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 task 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 task 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 awaitable Result 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 From fab91690633201034619d2f3714478fe3ab7a71b Mon Sep 17 00:00:00 2001 From: Julian Thurner Date: Tue, 4 Mar 2025 10:44:12 +0100 Subject: [PATCH 4/4] Review adjustments --- src/DotNext/Optional.cs | 8 -- src/DotNext/Result.cs | 142 +++++----------------- src/DotNext/Threading/Tasks/Conversion.cs | 6 +- 3 files changed, 32 insertions(+), 124 deletions(-) diff --git a/src/DotNext/Optional.cs b/src/DotNext/Optional.cs index 98fb8b414..35d4b731b 100644 --- a/src/DotNext/Optional.cs +++ b/src/DotNext/Optional.cs @@ -72,14 +72,6 @@ public static async Task> Convert(this Task> Convert(this Task> task, Converter>> converter) => await (await task.ConfigureAwait(false)).Convert(converter).ConfigureAwait(false); - /// - /// Creates a new instance of from the specified value. - /// - /// The type of the value. - /// The value to be placed to the container. - /// The value encapsulated by . - public static Optional From(T value) => new(value); - /// /// Creates from instance. /// diff --git a/src/DotNext/Result.cs b/src/DotNext/Result.cs index ae5238826..345d91284 100644 --- a/src/DotNext/Result.cs +++ b/src/DotNext/Result.cs @@ -89,30 +89,6 @@ public static Result FromError(TError e) where TError: struct, Enum => new(e); - private static AwaitableResult TransformAwaitableResult(this AwaitableResult task, Converter, Result> converter) - { - async Task ConvertInternal() - { - var result = await task.ConfigureAwait(false); - var conversionResult = converter(result); - return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; - } - - return ConvertInternal().SuspendException(); - } - - private static AwaitableResult TransformAwaitableResult(this AwaitableResult task, Converter, AwaitableResult> converter) - { - async Task ConvertInternal() - { - var result = await task.ConfigureAwait(false); - var conversionResult = await converter(result); - return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; - } - - return ConvertInternal().SuspendException(); - } - /// /// If successful result is present, apply the provided mapping function hiding any exception /// caused by the converter. @@ -123,7 +99,16 @@ async Task ConvertInternal() /// The type of the result of the mapping function. /// The conversion result. public static AwaitableResult Convert(this AwaitableResult task, Converter converter) - => task.TransformAwaitableResult((result) => result.Convert(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, @@ -135,19 +120,16 @@ public static AwaitableResult Convert(this AwaitableResult< /// The type of the result of the mapping function. /// The conversion result. public static AwaitableResult Convert(this AwaitableResult task, Converter> converter) - => task.TransformAwaitableResult((result) => result.Convert(converter)); + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + var conversionResult = result.Convert(converter); + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } - /// - /// 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) - => task.TransformAwaitableResult((result) => result.Convert(converter)); + return ConvertInternal().SuspendException(); + } /// /// If successful result is present, apply the provided mapping function. If not, @@ -158,15 +140,17 @@ public static AwaitableResult Convert(this AwaitableResult< /// 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) - => task.TransformAwaitableResult((result) => result.Convert(converter)); + public static AwaitableResult Convert(this AwaitableResult task, Converter> converter) + { + async Task ConvertInternal() + { + var result = await task.ConfigureAwait(false); + var conversionResult = await result.Convert(converter); + return conversionResult.IsSuccessful ? conversionResult.Value : throw conversionResult.Error; + } - /// - /// Converts the result into . - /// - /// Option monad representing value in this monad. - public static async Task> TryGet(this Task> task) - => (await task.ConfigureAwait(false)).TryGet(); + return ConvertInternal().SuspendException(); + } /// /// Converts the awaitable Result into a task holding . @@ -174,29 +158,6 @@ public static async Task> TryGet(this Task> task) /// 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 result into . - /// - /// Option monad representing value in this monad. - public static async Task> TryGet(this Task> task) - where TError : struct, Enum - => (await task.ConfigureAwait(false)).TryGet(); - - /// - /// Converts this task containing a Result to . - /// - /// The completed task representing the result. - public static AwaitableResult ToAwaitable(this Task> task) - { - async Task ConvertInternal() - { - var result = await task.ConfigureAwait(false); - return result.IsSuccessful ? result.Value : throw result.Error; - } - - return ConvertInternal().SuspendException(); - } } /// @@ -371,30 +332,6 @@ private AwaitableResult ConvertTask(TConverter con return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private AwaitableResult ConvertResultTask(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; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private AwaitableResult ConvertAwaitableResult(TConverter converter) where TConverter : struct, ISupplier> @@ -449,16 +386,6 @@ public Result Convert(Converter> converter) public AwaitableResult Convert(Converter> converter) => ConvertTask>>(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 AwaitableResult Convert(Converter>> converter) - => ConvertResultTask>>>(converter); - /// /// If successful result is present, apply the provided mapping function. If not, /// forward the exception. @@ -502,17 +429,6 @@ public unsafe Result Convert(delegate*> con public unsafe AwaitableResult Convert(delegate*> converter) => ConvertTask>>(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) - => ConvertResultTask>>>(converter); - /// /// If successful result is present, apply the provided mapping function. If not, /// forward the exception. diff --git a/src/DotNext/Threading/Tasks/Conversion.cs b/src/DotNext/Threading/Tasks/Conversion.cs index 9624a3f25..5d235be85 100644 --- a/src/DotNext/Threading/Tasks/Conversion.cs +++ b/src/DotNext/Threading/Tasks/Conversion.cs @@ -22,7 +22,7 @@ public static Task Convert(this Task task) where TInput : TOutput => task.Convert(Converter.Identity()); /// - /// Converts one type of task into another. + /// Converts one type of into another. /// /// The source Result type. /// The target Result type. @@ -43,7 +43,7 @@ public static async Task Convert(this Task tas => converter(await task.ConfigureAwait(false)); /// - /// Converts one type of task into another. + /// Converts one type of into another. /// /// The source Result type. /// The target Result type. @@ -84,7 +84,7 @@ public static async Task Convert(this Task tas => await converter(await task.ConfigureAwait(false)).ConfigureAwait(false); /// - /// Converts one type of awaitable Result into another. + /// Converts one type of into another. /// /// The source Result type. /// The target Result type.