-
Notifications
You must be signed in to change notification settings - Fork 140
Added convenience methods for easier Monad creation and Monad chaining #258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
@dotnet-policy-service agree |
@julianthurner , thanks for your contribution! According to the Contribution Guideline, could you change the target branch from |
Changing the target branch is not enough. Also, after the discussion of the API, units tests need to be added as well. |
e1ee1f1
to
f58043f
Compare
I would suggest to review the proposed API again from your side, since 5.19.0 has been released:
|
Looks nice👍I will review the changes and adjust them accordingly, sometime this week probably. |
Well, life got in the way ... |
@sakno Sorry it took me this long. My exams were eating up any brain capacity I had, but luckily they are over now. Please take a look at the updated version. I will add Unit Tests once the API is finished. |
src/DotNext/Optional.cs
Outdated
/// </summary> | ||
/// <param name="task">The task containing Optional value.</param> | ||
/// <returns>The converted optional value.</returns> | ||
public static async Task<Result<T>> ToResult<T>(this Task<Optional<T>> task) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Task<Result<T>>
is redundant type, this conversion makes no sense. There is Flatten
method that converts Task<Optional<T>>
to Task<T>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, I changed the implementation to return AwaitableResult<T>
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to do that, because SuspendException
provides the same semantics:
Task<Optional<int>> optional = ...;
Result<int> result = await optional.Flatten().SuspendException();
src/DotNext/Optional.cs
Outdated
/// <param name="task">The task containing Optional value.</param> | ||
/// <param name="error">The error code to apply if the value is not present.</param> | ||
/// <returns>The converted optional value.</returns> | ||
public static async Task<Result<T, TError>> ToResult<T, TError>(this Task<Optional<T>> task, TError error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Task<Result<T, TError>>
is a redundant type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the implementation to return AwaitableResult<T, TError>
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to do that, because there is an overload for SuspendException
that returns the necessary result:
public static AwaitableResult<T, TError> SuspendException<T, TError>(this Task<T> task,
Converter<Exception, TError> converter)
/// <param name="mapper">A mapping function to be applied to the value, if present.</param> | ||
/// <param name="token">The token that can be used to cancel the operation.</param> | ||
/// <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> | ||
public Task<Optional<TResult>> Convert<TResult>(Func<T, CancellationToken, Task<TResult>> mapper, CancellationToken token = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method is redundant, since SuspendException
returns Result<T>
that has implicit conversion to Optional<T>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method is redundant, since
SuspendException
returnsResult<T>
that has implicit conversion toOptional<T>
.
@sakno I'm not sure I understand. This method is intended to be used like this:
var optional = Optional.Some(42);
var convertedOptional = await optional.Convert(async (int x, CancellationToken y) => x.ToString());
What would be the correct way of implementing this with SuspendException instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same can be achieved with existing extension method:
var optional = Optional.Some(42);
Optional<string> convertedOptional = Task.FromResult(optional).Convert(async (value, token) => x.ToString());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SuspendException
as the last call in the chain always returns Result<T>
, so any async chain can be converted to Result<T>
or Optional<T>
in the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It means that public static async Task<Optional<TOutput>> Convert<TInput, TOutput>(this Task<Optional<TInput>> task, Func<TInput, CancellationToken, Task<Optional<TOutput>>> converter, CancellationToken token = default)
method is redundant as well
I added some convenience methods to the library. The Option class receives a few extension methods that allow for chaining async conversions:
This is really useful if you work with databases where data is fetched / transformed multiple times asynchronously before being filled into a DTO.
The same thing for Result:
This also forwards the exception up the chain just like with regular monads.
There's also overloads for converting Option ↔ Result in both directions if there's need for conversion within a chain.
Also, I added some additional convenience methods for creating Options and Results statically: