dotNext icon indicating copy to clipboard operation
dotNext copied to clipboard

Added convenience methods for easier Monad creation and Monad chaining

Open julianthurner opened this issue 9 months ago • 8 comments

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 avatar Feb 04 '25 14:02 julianthurner