paint-brush
Either Monad: A Functional Approach to Error Handling in C#.by@jmarenkov
1,250 reads
1,250 reads

Either Monad: A Functional Approach to Error Handling in C#.

by Jevgeni MarenkovFebruary 27th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The article introduces the Either monad as a sophisticated tool for error handling in C#, contrasting it with the simpler Option monad. Using the LanguageExt library, it demonstrates how Either effectively communicates not just the occurrence of an error, but also its nature, through a practical example involving user retrieval in a repository. By leveraging Either, developers can distinguish between successful outcomes and various error scenarios in a type-safe way, enhancing the robustness and maintainability of their code.
featured image - Either Monad: A Functional Approach to Error Handling in C#.
Jevgeni Marenkov HackerNoon profile picture


In functional programming, the Either monad serves a special purpose: it encapsulates a value that can be one of two types, traditionally called Left and Right. The Left represents an error state or failure, while the Right represents success.


We already have an Option that kind of says whether the value exists or not. However, that may not be enough all the time as it may be necessary to tell why the values are not there. Let us take a look at a simple example: we have a UserRepository - component responsible for user-related CRUD operations. Let’s imagine that we need an operation for retrieving the user by ID.


I will be using the LanguageExt library in C# for practical examples. Suppose we need a function to retrieve a user by their ID. A straightforward approach might look like this:


public User FindFromCache(string userId) => database.GetUser(userId);


But what happens if the user does not exist in the database? To address this, we might initially consider using the Option monad:


public Option<User> FindFromCache(string userId)=>
 Prelude.Option(database.GetUser(userId));


But what happens if data throws an exception? So, this is where the Either monad comes into play, providing a solution for error handling:


        public Either<RepositoryFailure, User> FindFromRepository(string userId) => 
        Prelude.Try(() => database
            .GetUser(userId))
            .ToEither()
            .MapLeft(f => UserFailure.DatabaseFailure(f));


In this approach, FindFromRepository can now return either a User object or a detailed error encapsulated in RepositoryFailure. This not only makes our error handling more intentional but also allows us to propagate error information across different layers of the service.


To support this, we define error-handling classes and helper classes as follows:


    public class Database
    {
        public User GetUser(string userId) => new("3", "name");
    }

    public record User(string Id, string Name) { }

    public static class UserFailure
    {
        public static RepositryFailure Fetch(Error error) => new RepositryFailure.Fetch(error);
        public static RepositryFailure DatabaseFailure(Error error) => new RepositryFailure.DatabaseFailure(error);
        public static BusinessLogicFailure ObjectNotFound(Error error) => new BusinessLogicFailure.ObjectNotFound(error);
    }

    public abstract class RepositryFailure
    {
        public Error Error { get; }
        public RepositryFailure(Error error) => Error = error;
        public class Fetch : RepositryFailure { public Fetch(Error error) : base(error) { } }
        public class DatabaseFailure : RepositryFailure { public DatabaseFailure(Error error) : base(error) { } }
        public class ObjectNotFound : RepositryFailure { public ObjectNotFound(Error error) : base(error) { } }
    }
    public abstract class BusinessLogicFailure
    {
        public Error Error { get; }
        public BusinessLogicFailure(Error error) => Error = error;
        public class ObjectNotFound : BusinessLogicFailure { public ObjectNotFound(Error error) : base(error) { } }
    }


The code can be found here https://github.com/jevgenimarenkov/language-ext-tutorials/blob/main/Either/UserRepository.cs