Quick Start

Using Monacs library is pretty easy. First, you have to install it into the project. You can do it from the UI in Visual Studio, using Package Manager Console:

Install-Package Monacs.Core

If you're using dotnet CLI you can do it as well:

dotnet add package Monacs.Core

Creating optional values

Now, let's define some data structure which will have an optional field. If you were creating expense tracking system you may have a class like this:

using Monacs.Core;

public class Expense
{
    public ExpenseCategory Category { get; set; }
    public decimal Amount { get; set; }
    public Option<string> Notes { get; set; }
}

Option<T> is a generic struct that wraps any value and it annotates that given property, function parameter or result can be empty. Let's create object of Expense class and see it in action:

using Monacs.Core;
...
public Expense CreateExpense(ExpenseCategory category, decimal amount, string notes) => new Expense
{
    Category = category,
    Amount = amount,
    Notes = string.IsNullOrEmpty(notes)
        ? Option.Some(notes)
        : Option.None<string>()
}

First thing you will notice is that there are no publicly available constructors for Option<T> type. You create them using Some and None factory methods. This is because the type can only have two states (again Some and None), and Monacs is doing everything to prevent you from having any other possibility here.

OK, so now Notes field will be Some when notes parameter was null or empty. The code to do it is quite verbose though. We can make it a bit shorter by leveraging using static feature of C#. The code could look like this:

using static Monacs.Core.Option;
...
public Expense CreateExpense(ExpenseCategory category, decimal amount, string notes) => new Expense
{
    Category = category,
    Amount = amount,
    Notes = string.IsNullOrEmpty(notes) ? Some(notes) : None<string>()
}

Things get nicer now. By statically importing Option class we can use methods defined there as they were defined in the same class we're in. Option class is a static class containing set of factory and extension methods that allow you to create and work with Option<T> struct.

Actually the code we've written there is so common that Monacs contains extension that does exact same thing:

using Monacs.Core;
...
public Expense CreateExpense(ExpenseCategory category, decimal amount, string notes) => new Expense
{
    Category = category,
    Amount = amount,
    Notes = notes.ToOption()
}

There are ToOption() overloads that allow you to convert any reference type (None when null), nullable (None when null) and string (None when null or empty). You should use them instead of Some and None factory methods whenever possible, as they prevent you from doing unwise things like this:

public static Option<T> ToEvilSome<T>(T value = null) where T : class =>
    Option.Some(value);

As you may guess, this code will create Some state with potentially null value - this isn't a very safe code, is it?

Working with optional values

Once you've created optional values you'll want to actually use it. Let's say you want to display notes when they are actually present, and display alternate text when it's empty. You can start by creating function like this:

using Monacs.Core;
...
public string GetNotesText(Option<string> notes) =>
    notes.IsSome
    ? notes.Value
    : "There are no notes, sorry!";

Using Value property is convenient, but you shouldn't actually do it unless necessary. In most functional languages you could use pattern matching as an alternative, but the one in C# (as of version 7.0) isn't powerful enough. But no worries, Monacs is here to help:

using Monacs.Core;
...
public string GetNotesText(Option<string> notes) =>
    notes.Match(
        some: n => n.Value
        none: () => "There are no notes, sorry!");

Now you won't accidentally use Value when it's not present. Match function is quite powerful, but it's also very very verbose. That's why for many common operations you can find helper methods in Monacs. Shorter version of the code above can look like this:

using Monacs.Core;
...
public string GetNotesText(Option<string> notes) =>
    notes.GetOrDefault(whenNone: "There are no notes, sorry!");

Combining calls

Once you got an optional value, you may want to transform it in one way or another. Getting back to the notes example, let's assume that the editor allows you to use Markdown for formatting and you want to get the word count from your field. To make it clear what are you doing you want to make it explicit, so the code can look like this:

using Monacs.Core;
...
public int GetWordCount(Option<string> notes)
{
    if (notes.IsNone)
        return 0;
    var strippedNotes = RemoveFormatting(notes.Value);
    return GetWordCount(strippedNotes);
}

As with earlier example, you can accidentaly use the Value when it's not set, so you probably want to use a bit different approach. With Monacs you can do it like this:

using Monacs.Core;
...
public int GetWordCount(Option<string> notes) =>
    notes
        .Map(RemoveFormatting)
        .Map(GetWordCount)
        .GetOrDefault();

Now the code is only using Value when it's actually set. Another important change is that the code is now an expression instead of set of instructions, making code briefer and removing unnecessary noise.

So you may ask what is this Map function? If you've ever used LINQ then probably you know Select function from it. Map is exactly the same thing, just operating on Option<T> instead of IEnumerable<T>. It takes in the option (as an extension of it) and a mapper function that accepts one value and returns other value. The signature of mapper in C# convention is Func<T1, T2>. Mapper is executed only when input option is Some, and it will return it's result wrapped into Some. Otherwise, it will return None<T2>. If the function above was to return Option<int>, giving None when there are no notes, code without Map could look like this:

using Monacs.Core;
using static Monacs.Core.Option;
...
public Option<int> GetWordCount(Option<string> notes)
{
    if (notes.IsNone)
        return None<int>();
    var strippedNotes = RemoveFormatting(notes.Value);
    var wordCount = GetWordCount(strippedNotes);
    return Some(wordCount);
}

Using Map it gets much simpler (and safer):

using Monacs.Core;
...
public Option<int> GetWordCount(Option<string> notes) =>
    notes.Map(RemoveFormatting).Map(GetWordCount);

So Map is really nice helper function, but what would happen if the GetWordCount function was returning Option<int>, giving None when there are no words? We would get compile error that the value returned doesn't match function signature. Now it would be Option<Option<int>>, which doesn't look good. Fortunatelly there is one more function that can solve this particular problem. It's called Bind. Let's see it in action, given the described case:

using Monacs.Core;
...
public Option<int> GetWordCount(Option<string> notes) =>
    notes.Map(RemoveFormatting).Bind(GetWordCount);

Now the returned value matches the signature again. We're good to go. So what's this Bind function? It is very similar to Map, the difference being signature of the function it accepts as a parameter. In Bind it's Func<T1, Option<T2>> and it's called binder. If the input to Bind is Some, it will return result of binder without wrapping it into Some, so it will actually return None when binder returns None. That allows for even more composability.

This should give you a brief overview of how to work with Option<T> type, but what about Result<T>? It turns out it works in the same way, so let's explore the differences.

Working with Result<T>

Similar to Option<T>, Result<T> type is a struct and has two possible states - Ok with a data of type T in the Value property, and Error with a data of type ErrorDetails in the Error property. This makes it perfect candidate for second common case, where you have a function and it may fail to execute properly. Usualy in such case you can expect an exception to be thrown. The problem with exceptions is that you don't have any explicit way to say that the function may fail and which exceptions it may throw (apart from comments). Handling exceptions is also problematic, as you need to decide when to wrap the code into try...catch - having it everywhere requires a lot of code, and it's also impacting performance quite substantialy. So just like Option<T> mitigates the problem of null, Result<T> deals with exceptions.

There is very similar set of extensions provided for Result<T> to the ones for Option<T>. The most important ones like Bind, Map and Match are there, as well as many others. There are also async versions of them, provided in Monacs.Core.Async namespace, such as BindAsync or MapAsync. You can then operate on Task<Result<T>>, have async binder or mapper functions and so on. One thing to remember is that once you jump into async code, you will have to keep using async variants to the the end of the function chain - until you await on the chain and get the underlying Result<T>. See the example below:

public async Task<Result<Unit>> UpdateCustomerAddress(UpdateCustomerAddressDto newCustomerAddress)
{
    var result = await Validate(newCustomerAddress)
        // Validate returns Result<UpdateCustomerAddressDto>
        .BindAsync(newAddress => GetCustomer(newAddress.Id).MapAsync(customer => (customer, newAddress)))
        // GetCustomer returns Task<Result<Customer>>, so we use MapAsync
        .MapAsync(UpdateCustomerAddress)
        // UpdateCustomerAddress takes tuple of Customer and UpdateCustomerAddressDto and returns Result<Customer>
        .BindAsync(PersistCustomer)
        // PersistCustomer returns Task<Result<Customer>>
        .DoWhenErrorAsync(LogError);

    return result.Ignore();
    // result is now Result<Customer>, we can ignore the value (if we use CQRS approach) using non-async extension
}

You can find list of available extensions in the API documentation.

Unit - type representing no value

To avoid need to duplicate the APIs for the functions that don't need to return anything (defined as void in C#) Monacs uses Unit type, known from other languages (like F#) and libraries (like Reactive Extensions). Unit Has only one value and it's available as Unit.Default property. You can use it as a substitute for any type whenever you need some, but you don't care about the value, e.g. you can return Task<Unit> (equivalent of non-generic Task) or Func<T> instead of Action. Result<Unit> can be used as a return type for functions with side effects that don't return any value, e.g. saving data to the database. There are some additional extensions provided for Result<Unit> in Monacs.Core.Unit namespace, like the mentioned above Ignore function.