The Monad Invasion - Part 2: Monads in Action!
Tags: vonage, dotnet, sdk, monads
Published: 2024-02-27 09:18:41

Hello friends,

In our previous post "[The Monad Invasion - Part 1: What's a Monad?](https://developer.vonage.com/en/blog/he-monad-invasion-part-1-whats-a-monad)", we chose a pragmatic approach to introduce Monads, incrementally building our own. 
By now, you should be familiar with the concept, understanding how to transform its value with either `.Map()` or `.Bind()` and how to extract it with `.Match()`.

In this article, we want to demonstrate practical applications for various Monads, and we'll use an example from the [Vonage .NET SDK](https://github.com/Vonage/vonage-dotnet-sdk). But first, let's have a quick recap.

## Quick Recap

We previously crafted the `Optional` Monad, which can exist in one of two states: `Some`, indicating the presence of a value, or `None`, indicating the absence of value.

```csharp
private static SchrodingerBox<int> Half(int value) =>
    value % 2 == 0 
        ? SchrodingerBox<int>.Some(value / 2)
        : SchrodingerBox<int>.None();

private static int Increment(int value) => value + 1;

SchrodingerBox<int>.Some(0)
    .Map(Increment) // Some(0) becomes Some(1)
    .Map(Increment) // Some(1) becomes Some(2)
    .Bind(Half) // Some(2) becomes Some(1)
    .Map(Increment) // Some(1) becomes Some(2)
    .Match(some => $"The value is some {some}!", () => "The value is none")
    .Should()
    .Be("The value is some 2!");

SchrodingerBox<int>.Some(0)
    .Map(Increment) // Some(0) becomes Some(1)
    .Bind(Half) // Some(1) becomes None - the Monad's state has changed
    .Map(Increment) // None remains None
    .Match(some => $"The value is some {some}!", () => "The value is none")
    .Should()
    .Be("The value is none");
```

Our box provides three essential functionalities:

* `.Map(operation)` empowers you to generate a new value based on an existing one and wrap the new value into a new box. The operation is only executed if the box is in `Some` state.
* `.Bind(operation)` is a transformation mechanism similar to `.Map()`. However, it differs by enabling the alteration of the box's state as the function returns a box instead of a value. Like `.Map()`, the operation is only executed if the box is in `Some` state.
* `.Match(some, none)` evaluates the box's state and invokes the corresponding function.

The box allows you to manipulate a value without knowing its current state.

In essence, the state becomes irrelevant as your sequence of operations remains the same. As you probably observed, the code has no branching constructs (if/else).

## When Is It Worth Using A Monad?

![](https://cdn.pixabay.com/photo/2016/01/05/17/49/good-1123013_1280.jpg "An image for exposing two different states")

Given their nature of opposing states, Monads prove to be irrelevant in scenarios with operations yielding a singular outcome. For Monads to be applicable, the operation must present at least two distinct possible results.

So far, we have worked with the `Optional` monad, but many more exist. To share examples of other monads, let's explore which monads the library [Language-Ext](https://github.com/louthy/language-ext) offers and find out their useful contexts.

### Option

Given our prior implementation, you should be pretty familiar with this one by now. It shines when wrapping an optional value or a value that may (or may not) exist.

```csharp
private async Task<Option<User>> FindUser(Guid id)
{
    var user = await this.repository.Users.FirstOrDefaultAsync(user => user.Id == id);
    return user ?? Option<User>.None;
}
```

### Try

The `Try<T>` Monad represents an operation that may encounter failure: 

* `Success` indicates the operation succeeded, yielding the result `<T>`.
* `Exception` indicates the operation resulted in an exception. The Monad will return the exception instead of throwing it.

```csharp
private static Try<decimal> Divide(decimal value, decimal divisor) => 
        Try(() => value / divisor);
```

In the above example, we can encapsulate a potentially "risky" operation within a `Try`.

* Invoking `Divide(50,2)` will return a `Success` state with the value `25`.
* Invoking `Divide(50,0)` will return a `Exception` state with a `DivideByZeroException`.

### Either

The `Either<L, R>` Monad represents an operation that can return two distinct types, defined by `Left`or `Right`. This Monad is extremely versatile, given `<L, R>` are both generics types. Nevertheless, the `Left` state usually represents errors or exceptional cases.

```csharp
private static Either<Error, decimal> Divide(decimal value, decimal divisor) =>
    divisor == 0
        ? new Error("Cannot divide by 0.")
        : value / divisor;

private record Error(string Message);
```

In line with the previous scenario:

* Invoking `Divide(50,2)` will return a `Right` state with the value `25`.
* Invoking `Divide(50,0)` will return a `Left` state with an `Error` record explaining why it wasn't processed.

### Validation

The `Validation<Fail, Success>` Monad represents a validation operation that may fail for multiple reasons. 

```csharp
private record User(string Firstname, string Lastname, MailAddress Email);

private static Validation<string, User> CreateUser(string firstname, string lastname, string email)
{
    var errors = new Seq<string>();
    if (string.IsNullOrWhiteSpace(firstname))
    {
        errors = errors.Add("Firstname cannot be empty.");
    }
    
    if (string.IsNullOrWhiteSpace(lastname))
    {
        errors = errors.Add("Lastname cannot be empty.");
    }

    if (!MailAddress.TryCreate(email, out var address))
    {
        errors = errors.Add("Invalid mail address.");
    }

    return errors.Any()
        ? Validation<string, User>.Fail(errors)
        : Validation<string, User>.Success(new User(firstname, lastname, address));
}
```

In this example:

* Invoking `CreateUser("Jane", "Doe", "jane.doe@email.com")` will return a `Success` with a valid `User` instance.
* Invoking `CreateUser(null, null, null)` (or any invalid value) will return a `Failure` with the collection of encountered errors.

## Referential Transparency

![](https://cdn.pixabay.com/photo/2018/09/12/18/19/mirror-3672873_1280.jpg "An image to illustrate transparency")

These Monads all have something in common: they're all **transparent** about an operation's potential outcomes, whether it can fail, throw an exception or return various values.
As such, the method's return type must communicate the result of those outcomes instead of an invisible and unpredictable mechanism (an exception).

Here, we're talking about [Referential Transparency](https://en.wikipedia.org/wiki/Referential_transparency). It applies when we can **always** replace an expression by its value.

```csharp
// Referentially Opaque examples
public User FindUser(Guid id) => this.users.First(user => user.Id == id);
public int Divide(int a, int b) => a/b;
public void SetName(string name)
{
    ArgumentNullException.ThrowIfNull(name);
    this.Name = name;
}

// Referentially Transparent examples
public Maybe<User> FindUser(Guid id) => this.users.FirstOrDefault(user => user.Id == id) ?? Maybe<User>.None;
public Try<decimal> Divide(decimal value, decimal divisor) => Try(() => value / divisor);
public int Add(int a, int b) => a+b;
public Either<Error, Unit> SetName(string name)
{
    if (name is null)
    {
        return new Error("Name cannot be null);
    }
    
    this.Name = name;
    return Unit.Value;
}
```

> You probably noticed that `.SetName()` returns a `Either<Error, Unit>`. You may have come across `Unit` in libraries like [MediatR](https://github.com/jbogard/MediatR) or [Language-Ext](https://github.com/louthy/language-ext). It's a simple construct representing a type with only one possible value. We use it as a placeholder for operations that do not return a value but may return another state. In our example, `.SetName()` is a Command that **does not return a value but may fail**. Therefore, the monad `Either<Error, Unit>` carries two possible states: Right (without value) or Left (with an Error).

Although Referential Transparency doesn't inherently solve specific problems, it significantly improves code predictability and readability. Making everything explicit in the method's signature minimizes the potential for the unexpected.
[Uncle Bob](http://cleancoder.com) emphasizes in his book "[Clean Code: A Handbook of Agile Software Craftsmanship](<>)" that "*the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code... Therefore, making it easy to read makes it easy to write*". This highlights the importance of code clarity and transparency in facilitating both comprehension and efficient development.

This was my primary motivation behind the making of "[Throw exceptions... out of your codebase](https://www.youtube.com/watch?v=vWDUk-3MSiU)".

## Vonage SDK Example

So far, we focused on relatively simple examples. However, what about using Monads in more complex flows? Let's look at an example from our [.NET SDK](https://github.com/Vonage/vonage-dotnet-sdk) involving our two-factor authentication API [Verify](https://developer.vonage.com/en/verify/overview).

### Custom Monads

Previously, we built an `Optional` and showed an existing set based on [Language-Ext](https://github.com/louthy/language-ext), but various libraries also offer Monad implementations. In the case of our [SDK](https://github.com/Vonage/vonage-dotnet-sdk), we deliberately avoided relying on external libraries like [Language-Ext](https://github.com/louthy/language-ext). Indeed, Monads are a part of the SDK's public API, and relying on an external library would introduce a dependency over which we would have limited control.

Our approach involved creating our custom Monad implementations tailored to the specific needs of the SDK. This strategy allowed us to maintain control over the design and functionality of these Monads while avoiding dependencies on external libraries.

Furthermore, our goal was to present a lightweight and user-friendly version of specific Monads, ensuring easier adoption from developers working with our SDK.

Our SDK implements the following Monads:

* `Result<T>` Monad, similar to an `Either<IFailure, T>` with a more concise syntax - C# being verbose regarding generics.
* `Maybe<T>` Monad, similar to an `Option<T>`.

### Example With Two-Factor Authentication

2FA is a two-step workflow. We first initiate an authentication process, resulting in the customer receiving a validation code based on the specified workflow (SMS, WhatsApp, Email, Voice, SilentAuth). Once the code is received, we send it to our API for verification.

```csharp
var myPhoneNumber = ...
var result = await StartVerificationRequest.Build()
    .WithBrand("Monads Inc.")
    .WithWorkflow(SmsWorkflow.Parse(myPhoneNumber))
    .Create() // Build the authentication request
    .BindAsync(request => verifyClient.StartVerificationAsync(request)) // Process the authentication request
    .BindAsync(VerifyCodeAsync) // Start the second step if it's a success
    .Match(AuthenticationSuccessful, AuthenticationFailed);

private async Task<Result<Unit>> VerifyCodeAsync(StartVerificationResponse response)
{
    var myCode = ... // Receive verification code based on the specified workflow
    return await response
        .BuildVerificationRequest(myCode) // Build the verification request
        .BindAsync(request => verifyClient.VerifyCodeAsync(request)); // Process the verification request
}
```

Everything we've covered so far remains applicable as our workflow contains fetching user input, parsing operations, and making API calls.

Once again, we successfully removed branching from our code, maintaining a consistent flow regardless of the Monad's state. It's worth noting our process can fail at five distinct locations:

* When building the authentication request
* When processing the authentication request
* When building the verification request
* When processing the verification request
* When calling the second step `VerifyCodeAsync`

If you remember, in our previous post, we talked about keeping a Monad active as long as possible. In this scenario, the value remains in our `Result<T>` from the beginning until the end of the flow, allowing us to chain the entire sequence of operations.

<signup></signup>

## To Be Or Not To Be... Pure?

![](https://cdn.pixabay.com/photo/2016/05/05/02/37/sunset-1373171_1280.jpg "An image to illustrate purity")

In our previous example, you may have noticed that our Monad did not remain *pure* during the workflow. Indeed, we provided *unpure* functions to `.Bind()`, like `request => verifyClient.StartVerificationAsync(request)` or `request => verifyClient.VerifyCodeAsync(request)`. Is it problematic, though?

By definition, a *pure* function **does not refer to any global state** and should **not produce any side effect**. It consistently produces an output that solely depends on the input, ensuring the same output for a specific input - in other words, high predictability.

While the concept of "pure monad" might be discussed, it's essential to understand that using monads does not necessarily require the Monad itself to be *pure*. Instead, monads are often used to structure computations involving *impure* operations while adhering to functional programming principles. Indeed, interactions with external resources, such as databases or APIs, introduce side effects.

## Impure Monads... With Exceptions?

The default behaviour ensures that our monads won't throw any exception, even if the parameter function within them does. This design choice helps us maintain `referential transparency`.

```csharp
public Result<TB> Bind<TB>(Func<T, Result<TB>> bind)
{
    try
    {
        return this.IsFailure
            ? Result<TB>.FromFailure(this.failure)
            : bind(this.success);
    }
    catch (Exception exception)
    {
        return SystemFailure.FromException(exception).ToResult<TB>();
    }
}

public Result<TB> Map<TB>(Func<T, TB> map)
{
    try
    {
        return this.IsFailure
            ? Result<TB>.FromFailure(this.failure)
            : Result<TB>.FromSuccess(map(this.success));
    }
    catch (Exception exception)
    {
        return SystemFailure.FromException(exception).ToResult<TB>();
    }
}
```

However, what if you prefer to stick with exceptions instead of extracting the value with `.Match()`? We wanted our monads to be versatile, so we introduced a `GetSuccessUnsafe()` functionality. This function will throw an exception if the monad is in the `Failure` state.

```csharp
public T GetSuccessUnsafe() => this.IfFailure(value => throw value.ToException());
```

The type and data of the exception depend on the underlying failure value:

* `ResultFailure` will throw a `VonageException`
* `ParsingFailure` will throw a `VonageException`
* `HttpFailure` will throw a `VonageHttpRequestException`
* `AuthenticationFailure` will throw a `VonageAuthenticationException`
* And so on...

Here's how you can incorporate exceptions into your monadic flow using the same example as before:

```csharp
try
{
    await StartVerificationRequest.Build()
        .WithBrand("Monads Inc.")
        .WithWorkflow(SmsWorkflow.Parse(myPhoneNumber))
        .Create()
        .BindAsync(request => verifyClient.StartVerificationAsync(request))
        .BindAsync(VerifyCodeAsync)
        .GetSuccessUnsafe(); // Will throw exception if in the Failure state
    AuthenticationSuccessful();
}
catch (Exception exception)
{
    AuthenticationFailed(exception);
}
```

Whether you choose the standard monadic approach or opt for exceptions, our goal is to accommodate different styles of error handling, ensuring that our monads align with your coding preferences.

## Wrapping Up

We've concluded the second post of our "The Monads Invasion" series. This article aimed to demonstrate various sets of Monads, including our custom implementations, and illustrate their use in extended workflows.

At this point, you should realize Monads offers an alternative approach to error handling in your workflows - and that's no coincidence. Indeed, our upcoming post will bring more light on the methodology behind the chaining of operations. Stay tuned for more!

If you have any questions or want to chat, feel free to hit me up on my [LinkedIn](https://www.linkedin.com/in/guillaumefaas/) or join us on the [Vonage Developer Slack](https://developer.vonage.com/en/community/slack).

Happy coding, and I'll catch you later!