Skip to content

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

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

julianthurner
Copy link

@julianthurner julianthurner commented Feb 4, 2025

I added some convenience methods to the library. The Option class receives a few extension methods that allow for chaining async conversions:

var optional = await Optional.FromValue(42)
    .Convert(async x => x > 10 ? Optional.FromValue(x * 2.0 - 20) : Optional<double>.None)
    .Convert(async x => x > 0 ? Optional.FromValue("Success") : Optional<string>.None);

Console.WriteLine(optional.HasValue ? optional.Value : "Optional has no value");

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:

var result = await Result.FromValue(42)
    .Convert(async x => x > 10 ? Result.FromValue(x * 2.0 - 20) : Result.FromException<double>(new Exception("x too small")))
    .Convert(async x => x > 0 ? Result.FromValue("Success") : Result.FromException<string>(new Exception("x less than zero")));

Console.WriteLine(result.IsSuccessful ? result : $"Failed with exception: {result.Error}");

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.

var optionalFromResult = await Task.FromResult(Result.FromValue<int, TestError>(42))
    .TryGet()
    .Convert(async x => x > 10 ? Optional.FromValue(x) : Optional<int>.None);
Console.WriteLine(optionalFromResult.HasValue ? optionalFromResult.Value : "Optional has no value");

var resultFromOptional = await Task.FromResult(Optional.FromValue(42))
    .ToResult()
    .Convert(async x => x > 10 ? Result.FromValue("Success") : Result.FromException<string>(new Exception("x too small")));
Console.WriteLine(resultFromOptional.IsSuccessful ? resultFromOptional.Value : $"Failed with exception: {resultFromOptional.Error}");

Also, I added some additional convenience methods for creating Options and Results statically:

var optionalWithValue = Optional.FromValue(42);
var errorCodeResultWithValue = Result.FromValue<int, TestError>(42);
var errorCodeResultWithError = Result.FromError<int, TestError>(TestError.Error1);

@julianthurner
Copy link
Author

@dotnet-policy-service agree

@dotnet-policy-service agree

@sakno
Copy link
Collaborator

sakno commented Feb 12, 2025

@julianthurner , thanks for your contribution! According to the Contribution Guideline, could you change the target branch from master to develop?

@sakno sakno self-assigned this Feb 12, 2025
@julianthurner julianthurner changed the base branch from master to develop February 12, 2025 13:25
@sakno
Copy link
Collaborator

sakno commented Feb 12, 2025

Changing the target branch is not enough. master cannot be used as a base branch for your branch, because master branch contains only squashed commits from develop branch. You need to create a branch from develop and cherrypick your commits, then force push.

Also, after the discussion of the API, units tests need to be added as well.

@julianthurner julianthurner force-pushed the master branch 2 times, most recently from e1ee1f1 to f58043f Compare February 26, 2025 13:35
@sakno
Copy link
Collaborator

sakno commented Mar 4, 2025

I would suggest to review the proposed API again from your side, since 5.19.0 has been released:

  1. Some factory method ✔️
  2. SuspendException to return Result<T> or Result<T, TError> ✔️
  3. Async Convert with cancellation support ✔️
  4. Flatten to convert Task<Optional<T>> to Task<T> ✔️

@julianthurner
Copy link
Author

I would suggest to review the proposed API again from your side, since 5.19.0 has been released:

  1. Some factory method ✔️
  2. SuspendException to return Result<T> or Result<T, TError> ✔️
  3. Async Convert with cancellation support ✔️

Looks nice👍I will review the changes and adjust them accordingly, sometime this week probably.

@julianthurner
Copy link
Author

Well, life got in the way ...
I started solving the merge conflicts today. But I'm in the middle of exams right now, so it's probably gonna take a little more time till I can continue.

@julianthurner
Copy link
Author

@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.

@julianthurner
Copy link
Author

@sakno If you approve of the current state, I will implement Unit Tests next.

/// <typeparam name="T">The type of the value.</typeparam>
/// <typeparam name="TResult">The type of the result of the mapping function.</typeparam>
/// <returns>The conversion result.</returns>
public static AwaitableResult<TResult> Convert<T, TResult>(this AwaitableResult<T> task, Converter<T, TResult> converter)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AwaitableResult is a leaf type. It cannot be used to build a chain, as discussed above. Please review the rest of the methods where the type is used as an input parameter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants