Skip to content

Commit b645fe8

Browse files
committed
Refactored monad chaining methods
1 parent f933aba commit b645fe8

File tree

3 files changed

+415
-0
lines changed

3 files changed

+415
-0
lines changed

src/DotNext/Optional.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace DotNext;
1010

11+
using System.Threading.Tasks;
1112
using Runtime.CompilerServices;
1213
using Intrinsics = Runtime.Intrinsics;
1314

@@ -58,6 +59,18 @@ public static async Task<T> Flatten<T>(this Task<Optional<T>> task)
5859
public static async Task<Optional<TOutput>> Convert<TInput, TOutput>(this Task<Optional<TInput>> task, Converter<TInput, TOutput> converter)
5960
=> (await task.ConfigureAwait(false)).Convert(converter);
6061

62+
/// <summary>
63+
/// If a value is present, apply the provided mapping function to it, and if the result is
64+
/// non-null, return an Optional describing the result. Otherwise returns <see cref="Optional{T}.None"/>.
65+
/// </summary>
66+
/// <typeparam name="TInput">The type of stored in the Optional container.</typeparam>
67+
/// <typeparam name="TOutput">The type of the result of the mapping function.</typeparam>
68+
/// <param name="task">The task containing Optional value.</param>
69+
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
70+
/// <returns>An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise <see cref="Optional{T}.None"/>.</returns>
71+
public static async Task<Optional<TOutput>> Convert<TInput, TOutput>(this Task<Optional<TInput>> task, Converter<TInput, Optional<TOutput>> converter)
72+
=> (await task.ConfigureAwait(false)).Convert(converter);
73+
6174
/// <summary>
6275
/// If a value is present, apply the provided mapping function to it, and if the result is
6376
/// non-null, return an Optional describing the result. Otherwise, returns <see cref="Optional{T}.None"/>.
@@ -79,6 +92,66 @@ public static async Task<Optional<TOutput>> Convert<TInput, TOutput>(this Task<O
7992
: Optional<TOutput>.None;
8093
}
8194

95+
/// <summary>
96+
/// If a value is present, apply the provided mapping function to it, and if the result is
97+
/// non-null, return an Optional describing the result. Otherwise returns <see cref="Optional{T}.None"/>.
98+
/// </summary>
99+
/// <typeparam name="TInput">The type of stored in the Optional container.</typeparam>
100+
/// <typeparam name="TOutput">The type of the result of the mapping function.</typeparam>
101+
/// <param name="task">The task containing Optional value.</param>
102+
/// <param name="converter">A mapping function to be applied to the value, if present.</param>
103+
/// <param name="token">The token that can be used to cancel the operation.</param>
104+
/// <returns>An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise <see cref="Optional{T}.None"/>.</returns>
105+
public static async Task<Optional<TOutput>> Convert<TInput, TOutput>(this Task<Optional<TInput>> task,
106+
Func<TInput, CancellationToken, Task<Optional<TOutput>>> converter, CancellationToken token = default)
107+
{
108+
var optional = await task.ConfigureAwait(false);
109+
return optional.HasValue
110+
? await converter.Invoke(optional.ValueOrDefault, token).ConfigureAwait(false)
111+
: optional.IsNull && Intrinsics.IsNullable<TOutput>()
112+
? new(default)
113+
: Optional<TOutput>.None;
114+
}
115+
116+
/// <summary>
117+
/// Creates <see cref="Result{T}"/> from <see cref="Optional{T}"/> instance.
118+
/// </summary>
119+
/// <param name="optional">The optional value.</param>
120+
/// <returns>The converted optional value.</returns>
121+
public static Result<T> ToResult<T>(this in Optional<T> optional)
122+
=> Result<T>.FromOptional(optional);
123+
124+
/// <summary>
125+
/// Creates <see cref="Result{T}"/> from <see cref="Optional{T}"/> instance.
126+
/// </summary>
127+
/// <param name="task">The task containing Optional value.</param>
128+
/// <returns>The converted optional value.</returns>
129+
public static async Task<Result<T>> ToResult<T>(this Task<Optional<T>> task)
130+
=> Result<T>.FromOptional(await task.ConfigureAwait(false));
131+
132+
/// <summary>
133+
/// Creates <see cref="Result{T, TError}"/> from <see cref="Optional{T}"/> instance.
134+
/// </summary>
135+
/// <param name="optional">The optional value.</param>
136+
/// <param name="error">The error code to apply if the value is not present.</param>
137+
/// <returns>The converted optional value.</returns>
138+
public static Result<T, TError> ToResult<T, TError>(this in Optional<T> optional, TError error)
139+
where TError : struct, Enum
140+
=> optional.HasValue ? new(optional.Value) : new(error);
141+
142+
/// <summary>
143+
/// Creates <see cref="Result{T, TError}"/> from <see cref="Optional{T}"/> instance.
144+
/// </summary>
145+
/// <param name="task">The task containing Optional value.</param>
146+
/// <param name="error">The error code to apply if the value is not present.</param>
147+
/// <returns>The converted optional value.</returns>
148+
public static async Task<Result<T, TError>> ToResult<T, TError>(this Task<Optional<T>> task, TError error)
149+
where TError : struct, Enum
150+
{
151+
var optional = await task.ConfigureAwait(false);
152+
return optional.HasValue ? new(optional.Value) : new(error);
153+
}
154+
82155
/// <summary>
83156
/// If a value is present, returns the value, otherwise throw exception.
84157
/// </summary>
@@ -609,6 +682,47 @@ public Optional<TResult> Convert<TResult>(Converter<T, Optional<TResult>> mapper
609682
public unsafe Optional<TResult> Convert<TResult>(delegate*<T, Optional<TResult>> mapper)
610683
=> ConvertOptional<TResult, Supplier<T, Optional<TResult>>>(mapper);
611684

685+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
686+
private async Task<Optional<TResult>> ConvertTask<TResult>(Func<T, CancellationToken, Task<TResult>> converter, CancellationToken token = default)
687+
=> HasValue ? await converter.Invoke(value, token).ConfigureAwait(false) : Optional<TResult>.None;
688+
689+
/// <summary>
690+
/// If a value is present, apply the provided mapping function to it, and if the result is
691+
/// non-null, return an Optional describing the result. Otherwise, returns <see cref="None"/>.
692+
/// </summary>
693+
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
694+
/// <param name="mapper">A mapping function to be applied to the value, if present.</param>
695+
/// <param name="token">The token that can be used to cancel the operation.</param>
696+
/// <returns>An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise <see cref="None"/>.</returns>
697+
public Task<Optional<TResult>> Convert<TResult>(Func<T, CancellationToken, Task<TResult>> mapper, CancellationToken token = default)
698+
=> ConvertTask(mapper, token);
699+
700+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
701+
private Task<Optional<TResult>> ConvertOptionalTask<TResult, TConverter>(TConverter converter)
702+
where TConverter : struct, ISupplier<T, Task<Optional<TResult>>>
703+
=> HasValue ? converter.Invoke(value) : Task.FromResult(Optional<TResult>.None);
704+
705+
/// <summary>
706+
/// If a value is present, apply the provided mapping function to it, and if the result is
707+
/// non-null, return an Optional describing the result. Otherwise, returns <see cref="None"/>.
708+
/// </summary>
709+
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
710+
/// <param name="mapper">A mapping function to be applied to the value, if present.</param>
711+
/// <returns>An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise <see cref="None"/>.</returns>
712+
public Task<Optional<TResult>> Convert<TResult>(Converter<T, Task<Optional<TResult>>> mapper)
713+
=> ConvertOptionalTask<TResult, DelegatingConverter<T, Task<Optional<TResult>>>>(mapper);
714+
715+
/// <summary>
716+
/// If a value is present, apply the provided mapping function to it, and if the result is
717+
/// non-null, return an Optional describing the result. Otherwise returns <see cref="None"/>.
718+
/// </summary>
719+
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
720+
/// <param name="mapper">A mapping function to be applied to the value, if present.</param>
721+
/// <returns>An Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise <see cref="None"/>.</returns>
722+
[CLSCompliant(false)]
723+
public unsafe Task<Optional<TResult>> Convert<TResult>(delegate*<T, Task<Optional<TResult>>> mapper)
724+
=> ConvertOptionalTask<TResult, Supplier<T, Task<Optional<TResult>>>>(mapper);
725+
612726
/// <inheritdoc cref="IFunctional{TDelegate}.ToDelegate()"/>
613727
Func<object?> IFunctional<Func<object?>>.ToDelegate() => Func.Constant<object?>(kind is NotEmptyValue ? value : null);
614728

0 commit comments

Comments
 (0)