Bearded.Monads 1.4.45

dotnet add package Bearded.Monads --version 1.4.45
NuGet\Install-Package Bearded.Monads -Version 1.4.45
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="Bearded.Monads" Version="1.4.45" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Bearded.Monads --version 1.4.45
#r "nuget: Bearded.Monads, 1.4.45"
#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 Bearded.Monads as a Cake Addin
#addin nuget:?package=Bearded.Monads&version=1.4.45

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

Bearded.Monads

Publish Bearded.Monads

Monads for use in C#. These include implementations of SelectMany (aka. bind) so you can use C#s fluent linq syntax.

Currently provides Option and Either, as they are useful for error checking, as well as task.

Also provides applicative instances for Task and Maybe.

Installation

Bearded.Monads is available from NuGet:

Install-Package Bearded.Monads

Then, just add using Bearded.Monads; to the top of your C# source file. There is also a Bearded.Monads.Syntax module that you can reference using using static Bearded.Monads.Syntax; with your other using statements.

Option

An obvious implementation which provides various helper methods. This allows you to avoid passing back null from methods and perfoming some special logic. By using Option, you enable the compiler to check that you've handled the missing case.

Usage

As an academic example, consider a trivial method that might have looked thusly without Option:

public bool TryParseInt(string input, out int output)
{
    if(int.TryParse(input, out int i))
    {
        output = i;
        return true;
    }
    output = default
    return false;
}

Can now be transformed into something a little more sane:

public Option<int> TryParseInt(string input)
{
    if(int.TryParse(input, out int i))
    {
        // Note the implicit operator that converts the `A` to an `Option<A>`
        return i;
    }
    return Option<int>.None;
}

As a more realistic example, imagine loading something from a database:

public MyEntity LoadEntityBy(int id)
{
    // Assume Dapper is being used...
    using(var connection = new IDbConnection(_connectionString))
    {
        string sql = "SELECT * FROM Entity WHERE Id = @Id";
        var result = connection.Query<DbEntity>(sql, new { Id = id });
        
        if(result.Any())
        {
            return _mapper.From(result.First()).To(new MyEntity());
        }
        return null;
    }
}

Becomes:

public Option<MyEntity> LoadEntityBy(int id)
{
    // Assume Dapper is being used...
    using(var connection = new IDbConnection(_connectionString))
    {
        string sql = "SELECT * FROM Entity WHERE Id = @Id";
        var result = connection.Query<DbEntity>(sql, new { Id = id });
        
        if(result.Any())
        {
            return _mapper.From(result.First()).To(new MyEntity());
        }
        return Option<MyEntity>.None;
    }
}

This doesn't seem to add much, but if you compose them:

public void LoadEntity(string fromPossibleId)
{
    var maybeEntity = from id in TryParseInt(fromPossibleId)
                        from entity in LoadEntityBy(id)
                        select entity;
    // This will shortcircuit if none of these work.
}

Either

This has been renamed from EitherSuccessOrFailure, and takes it's "success" value as the first type parameter.

This was done as Either "short circuits" on a Left in haskell, but this seems a little unnatural from C#. Please raise an issue if you don't believe this is the case.

Usage

This is useful to return some error condition from a long chain of Either results (or even via the AsEither when dealing with Option results).

For example, if I have the following chain of optional results:

public Option<ResultFromTertiaryService> LoadFromAMultitudeOfServices(string value)
{
    return from id in TryParseInt(value)
            from first in ExpensiveCallToAWebServiceThatMightFail(id)
            from second in TryAndLoadARecordFromTheDatabase(id, first.ClientData.SomeField)
            from third in TryAndFindDataInTertiaryService(id, second.AnotherField, first.Some.Other.Context)
            select third;
}

This might fail at any point, so it's helpful to tag the None with some helpful context by using AsEither to convert from Option<Success> to Either<Success, string> e.g.

public Either<ResultFromTertiaryService,string> LoadFromAMultitudeOfServices(string value)
{
    return from id in TryParseInt(value).AsEither("Failed to parse ({0}) into an id", value)
            from first in ExpensiveCallToAWebServiceThatMightFail(id).AsEither("Didn't find a value")
            from second in TryAndLoadARecordFromTheDatabase(id, first.ClientData.SomeField).AsEither("Couldn't find {0} in the database", id)
            from third in TryAndFindDataInTertiaryService(id, second.AnotherField, first.Some.Other.Context).AsEither("Failed to load from tertiary source")
            select third;
}

Try

This is similar to Either, but uses an exception in the error case. This is useful for things like the SafeCallback extension method over all objects, which provides an ergonomic version of wrapping everything in a try-catch block.

Task Applicative (aka Asynquence)

This is probable the most interesting use of Task. This allows one to chain together a sequence of tasks and provide a callback at the end to produce a final result.

It's recommended to use the below to bring the class into scope directly.

using static Bearded.Monads.Syntax;

Then usage is as follows:

            var result = await Asynquence(Task.FromResult(10))
                .And(Task.FromResult(10))
                .And(Task.FromResult(10))
                .And(Task.FromResult(10))
                .Select((a, b, c, d) => a + b + c + d);
            var expected = 40;

            Assert.Equal(expected, result);
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Bearded.Monads:

Package Downloads
Csn.Trade.Utils

Package Description

Csn.Trade.ApiUtils

Package Description

confudge

Confudge makes it easy to use typed configuration without the overhead of creating adapter objects. It uses a convention and some overridable defaults to quickly enable typed configuration in .net.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.4.45 1,163 1/3/2024
1.4.43 308 1/3/2024
1.4.42 630 12/3/2023
1.4.40 2,461 2/3/2023
1.4.39 752 2/3/2023
1.4.1 792 2/3/2023
1.3.76 34,102 12/20/2018
1.3.75 1,286 12/20/2018
1.3.74 1,223 12/20/2018
1.3.73 1,324 11/26/2018
1.3.72 1,659 11/13/2018
1.3.69 1,317 10/29/2018
1.3.67 1,273 10/26/2018
1.3.60 21,297 4/23/2018
1.3.55 4,385 2/27/2018
1.3.48 4,098 11/14/2017
1.3.46 1,526 9/20/2017
1.3.45 1,442 9/14/2017
1.3.44 1,448 9/13/2017
1.3.43 1,483 9/12/2017
1.3.41 1,452 9/12/2017
1.3.40 1,492 9/6/2017
1.3.37 4,725 9/6/2017
1.3.36 1,452 9/6/2017
1.3.35 1,515 9/6/2017
1.3.34 1,459 9/6/2017
1.3.31 1,491 9/5/2017
1.3.30 1,494 9/5/2017
1.3.28 1,473 9/5/2017
1.3.27 4,234 8/23/2017
1.3.26 8,585 8/21/2017
1.3.25 8,748 8/16/2017
1.3.23 1,479 8/14/2017
1.3.21 1,476 8/3/2017
1.3.20 1,560 8/3/2017
1.3.18 1,433 8/3/2017
1.3.16 1,476 8/2/2017
1.3.15 1,691 8/1/2017
1.3.14 1,487 7/28/2017
1.3.13 1,553 7/6/2017
1.3.12 1,607 6/22/2017
1.3.11 1,568 6/22/2017
1.3.10 1,516 6/22/2017
1.3.9 1,540 5/25/2017
1.3.8 1,551 5/24/2017
1.2.2 1,533 5/24/2017
1.2.1 1,616 4/26/2017
1.1.5 5,216 8/18/2016
1.1.4 1,830 7/12/2016
1.1.3 2,031 6/12/2016
1.1.2 2,272 5/31/2016
1.1.1 1,691 5/9/2016
1.1.0 1,654 2/15/2016
1.0.25 1,813 10/29/2015
1.0.24 1,741 9/25/2015
1.0.23 1,709 9/24/2015
1.0.22 1,697 9/16/2015
1.0.21 1,709 9/4/2015
1.0.20 1,839 6/26/2015
1.0.19 1,779 6/24/2015
1.0.18 1,665 6/3/2015
1.0.17 1,906 6/3/2015
1.0.16 1,678 5/6/2015
1.0.15 2,530 11/30/2014
1.0.14 1,804 11/25/2014
1.0.13 1,757 10/22/2014
1.0.12 1,882 10/7/2014
1.0.11 1,805 10/7/2014
1.0.10 2,083 8/3/2014
1.0.9 1,789 8/3/2014
1.0.8 1,684 8/3/2014
1.0.7 1,879 7/30/2014
1.0.6 1,866 6/25/2014
1.0.5 1,784 6/25/2014
1.0.4 1,801 6/16/2014
1.0.3 2,083 3/24/2014
1.0.2 1,858 3/19/2014
1.0.0 1,752 3/19/2014