OneOf.Monads 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package OneOf.Monads --version 1.0.0                
NuGet\Install-Package OneOf.Monads -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OneOf.Monads" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add OneOf.Monads --version 1.0.0                
#r "nuget: OneOf.Monads, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install OneOf.Monads as a Cake Addin
#addin nuget:?package=OneOf.Monads&version=1.0.0

// Install OneOf.Monads as a Cake Tool
#tool nuget:?package=OneOf.Monads&version=1.0.0                

Monads Based on the OneOf Union Type

This library adds common monads to the fantastic OneOf union type library.

The Option Monad

The Option<T> monad extends OneOf<None, Some<T>> and is modeled after F#'s Option Type. It is functionally similar to Haskell's Maybe Monad.

This monad provides a mechanism for conditional execution in a workflow/pipeline-style manner. Great for readability and error handling without try/catch.

Option.Bind Example

Bind is used to create a contract that will resolve to Some<T> when all checks have passed. It will resolve to None if a Bind statement returns None. All subsequent Bind statements will be skipped.

In this example we create a contract that guarantees that a given number is greater than 10 and is even. We then pipe the result into a Match to conditionally execute for both cases.

using OneOf.Monads

Option<int> IsGreaterThan10(int i)
        => i > 10 ? new Some<int>(i) : new None();

Option<int> IsEven(int i)
    => i % 2 == 0 ? new Some<int>(i) : new None();

[Theory]
[InlineData(12)]
[InlineData(24)]
void Conditional_execution_when_contract_is_fulfilled(int evenNumber)
{
    var expected = evenNumber;
    var option = Option<int>.Some(evenNumber);

    var actual = option
                    .Bind(IsGreaterThan10)
                    .Bind(IsEven)
                    .Match(
                        none => 0,
                        some => some.Value); 

    Assert.Equal(expected, actual);
}

Option.Map Example

Map is used to map a regular value of type T to an Option<T>. In the example below it is combined with the Bind and Match functions to apply type and format conversions. For instance it takes the string output of int.ToString() and returns an Option<string> that can be used to continue the pipeline. Map will not execute if the current value is None, instead it will simply resolve to None, meaning that the pipeline will not break.

using OneOf.Monads

[Fact]
public void Convert_to_option_type_using_map()
{
    var expected = "~20~";
    var option = Option<int>.Some(20);

    var actual = option
                    .Bind(IsGreaterThan10)
                    .Bind(IsEven)
                    .Map(i => i.ToString())
                    .Map(s => $"~{s}~")
                    .Match(
                        none => "could not convert number",
                        some => some.Value);

    Assert.Equal(expected, actual);
}

The Result Monad

The Result<TError, TSuccess> monad is similar to the Option<T> monad, but it also defines a value for the negative case, expressed as TError. Instead of the Bind function, it uses the control flow semantic AndThen. It also has the GetOrElse function that is used to define a fallback value for a pipeline. This monad is inspired by Kotlin and provides readable data transformation pipelines and monadic error handling.

Result.AndThen Example

Here is an example of a control flow that uses AndThen in combination with Map and Switch. AndThen can be chained and will not execute if the previous step returns the error case Error<TError>.

const string ErrorMessage = "division by zero";
private Result<string, int> Divide(int number, int by)
    => by == 0
        ? new Error<string>(ErrorMessage)
        : new Success<int>(number / by);

[Fact]
public void Success_and_error_both_have_values()
{
    var expectedSuccess = (12 / 2 / 2) * 2;
    var expectedError = ErrorMessage;

    Action<int> doMath = (divideBy)
        => Divide(12, divideBy)
            .AndThen(result => Divide(result, 2))
            .Map(result => result * 2)
            .Switch(
                error => throw new DivideByZeroException(error.Value),
                success => Assert.Equal(expectedSuccess, success.Value));

    doMath(2);

    var exception = Record.Exception(() => doMath(0));
    Assert.IsType<DivideByZeroException>(exception);
    Assert.Equal(expectedError, exception.Message);
}

Result.GetOrElse Example

This example demonstrates both how to define a fallback function and how to use the TError value, to provide logic on failure.

[Fact]
public void GetOrElse_lets_you_define_fallback_values()
{
    var maxLimitException = new Exception();
    maxLimitException.Data.Add("max", 25);

    Func<int, Result<Exception, int>> add5 = (val) => new Success<int>(val + 5);
    Func<int, Result<Exception, int>> checkIsBelow25 = (val) =>
            val > 25
            ? new Error<Exception>(maxLimitException)
            : new Success<int>(val);

    Func<int, int> add10ReturnMax25 = (start)
        => Result<Exception, int>.Success(start)
            .AndThen(add5)
            .AndThen(add5)
            .AndThen(checkIsBelow25)
            .GetOrElse(exception => (int)exception.Data["max"]);

    Assert.Equal(20, add10ReturnMax25(10));
    Assert.Equal(25, add10ReturnMax25(15));
    Assert.Equal(25, add10ReturnMax25(20));
}
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

NuGet packages (1)

Showing the top 1 NuGet packages that depend on OneOf.Monads:

Package Downloads
BasicWebHooks.MassTransit

MassTrasit requests and handlers for basic web-hooks.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.21.0 55,551 9/1/2022
1.20.0 397 8/25/2022
1.19.0 399 8/24/2022
1.18.0 419 8/2/2022
1.17.0 3,517 7/10/2022
1.16.0 426 7/5/2022
1.15.0 436 5/13/2022
1.13.0 442 3/22/2022
1.12.0 435 3/22/2022
1.11.0 398 3/21/2022
1.10.0 427 3/16/2022
1.9.0 417 3/14/2022
1.8.0 413 3/11/2022
1.7.0 426 2/22/2022
1.0.0 455 2/22/2022