The Maybe Monad in C#: More methods

Posted by: Yacoub Massad , on 2/12/2020, in Category Patterns & Practices
Views: 2417
Abstract: The Maybe Mona is a container that represents a value that may or may not exist. In this tutorial, I will go through some methods that make working with the Maybe monad easier.

In a previous article,The Maybe Monad, I talked about the Maybe Monad: a container that represents a value that may or may not exist.

In that article, I ended up with an implementation of Maybe that is a struct. Here is an excerpt from the code:

public struct Maybe<T>
{
    private readonly T value;

    private readonly bool hasValue;

    private Maybe(T value)
    {
        this.value = value;
        hasValue = true;
    }
    //...
}

I also provided a static Maybe class that makes it easier to create instances of Maybe<T>. For example, the following code creates a Maybe<string> that contains no value, and another one that contains the value “computer”:

Maybe<string> none = Maybe.None;

Maybe<string> some = Maybe.Some("computer");

I also talked about many other methods that make working with Maybe easier; for example, the Map and Bind methods.

In this article, I will talk about more useful methods that are related to Maybe.

Using ValueOr and ValueOrMaybe to handle the case where there is no value

The ValueOr method can be used to provide a default value in case the Maybe object does not contain a value. For example:

static void Test8()
{
    var errorMessage =
        GetErrorDescription(15)
            .ValueOr("Unknown error"); //Signature: T ValueOr(T defaultValue)
}

The GetErrorDescription method (discussed in the previous article) returns a Maybe<string> representing the description of the error for the specified code. It returns None (Maybe with no value) in case there is no defined description for the specified error code. The type of the errorMessage variable here is string, not Maybe<string>.

errorMessage will always have a value. If the GetErrorDescription method returns None, the default “Unknown error” value will be returned and stored inside errorMessage.

Now consider this code:

var errorMessage =
        GetErrorDescription(15)
            .ValueOr(GetDefaultErrorMessage());

Here, the default value is obtained by calling a method called GetDefaultErrorMessage. The GetDefaultErrorMessage method will always be called here, even if GetErrorDescription returns a value. This could be an issue if GetDefaultErrorMessage is expensive in terms of performance or if it has side effects that we only want to have if GetErrorDescription returned None.

There is another overload of ValueOr defined that allows us to provide a default value factory function that will only be called if the Maybe has no value:

static void Test9()
{
    var errorMessage =
        GetErrorDescription(15)
            //Signature: T ValueOr(Func<T> defaultValueFactory)
            .ValueOr(() => GetDefaultErrorMessage());
}

There is another variation of ValueOr defined in the Maybe struct. I call it ValueOrMaybe. It is used to provide an alternative Maybe value if the Maybe object at hand has no value. For example:

static void Test10()
{
    var errorMessage =
        GetErrorDescription(15)
     //Signature: Maybe<T> ValueOrMaybe(Maybe<T> alternativeValue)
            .ValueOrMaybe(GetErrorDescriptionViaWebService(15)) 
            .ValueOr("Unknown error");
}

static void Test11()
{
    var errorMessage =
        GetErrorDescription(15)
            //Signature: Maybe<T> ValueOrMaybe(Func<Maybe<T>> alternativeValueFactory)
            .ValueOrMaybe(() => GetErrorDescriptionViaWebService(15))              
            .ValueOr("Unknown error");
}

In Test10, we first try to get the error description via the GetErrorDescription method which tries to find the error description in some file. We invoke ValueOrMaybe on the returned Maybe<string> to obtain the error description from some web service to use it in the case where the first Maybe has no value.

The difference between the overload of ValueOrMaybe used in Test10 and the one used in Test11 is that in Test11, the GetErrorDescriptionViaWebService method will only be called if the first Maybe has no value. In Test10, it will always be called, even if we are not going to use its value.

Using ValueOrThrow to get the value or throw an exception if there is not a value

Consider this example:

static void Test12()
{
    var logContents =
        GetLogContents(1)
            // Signature: T ValueOrThrow(string errorMessage)
            .ValueOrThrow("Unable to get log contents"); 
}

The ValueOrThrow method above will cause an exception to be thrown if GetLogContents returns None. Otherwise, the log contents will be returned.

Other ValueOr variations

You can create different variations of ValueOr methods based on the type of the value. For example, consider the ValueOrEmptyArray extension method:

public static T[] ValueOrEmptyArray<T>(this Maybe<T[]> maybe)
{
    return maybe.ValueOr(Array.Empty<T>());
}

Another example is the ValueOrEmptyString extension methods:

public static string ValueOrEmptyString(this Maybe<string> maybe)
{
    return maybe.ValueOr(string.Empty);
}

Using GetItemsWithValue

Consider this example:

static void Test14()
{
    List<string> multipleLogContents =
        Enumerable.Range(1, 20)
            .Select(x => GetLogContents(x))
     //Signature: IEnumerable<T> GetItemsWithValue<T>(this IEnumerable<Maybe<T>> enumerable)
            .GetItemsWithValue()
            .ToList();
}

Here, we invoke GetLogContents twenty times. The Select method returns an enumerable of type IEnumerable<Maybe<string>>. GetItemsWithValue enables us to obtain an IEnumerable<string> that corresponds to the maybe objects that do have values. The ones without a value will not be included in the returned enumerable.

Using IfAllHaveValues

Consider this example:

static void Test15()
{
    List<string> multipleLogContents =
        Enumerable.Range(1, 20)
            .Select(x => GetLogContents(x))
            //Signature: Maybe<IEnumerable<T>> IfAllHaveValues<T>(this IEnumerable<Maybe<T>> enumerable)
            .IfAllHaveValues()
            .ValueOrThrow("Some logs are not available")
            .ToList();
}

IfAllHaveValues will return None if any item in the enumerable has no value. In this example, if any of the 20 logs is unavailable, IfAllHaveValues would return None and ValueOrThrow would throw an exception.

Notice the signature of IfAllHaveValues, it takes an IEnumerable<Maybe<T>> and returns a Maybe<IEnumerable<T>>.

Using ToAddIfHasValue

Consider this example:

static void Test16()
{
    Maybe<string> logMaybe = Maybe.Some("entry9");

    var list = new List<string>()
    {
        "entry1",
        logMaybe.ToAddIfHasValue(),
        "entry2"
    };
}

In the above method, we create a list of strings. We want the list to have “entry1”, “entry2”. Also, if logMaybe has a value, we want its value to be included between “entry1” and “entry2”.

The list variable will contain a list that will either have two or three entries inside it depending on whether logMaybe has a value. In the code we just saw, we know that it has the value “entry9”.

This is possible in C# because the list initializer syntax is extensible. You can have a value, say of type TValue, in the initialization list as long as there is a method with a signature similar to:

void Add(this TCollection collection, TValue value)

Where TCollection is the type of the list we are trying to initialize.

The following version of Test16 is equivalent to the version displayed earlier:

static void Test16()
{
    Maybe<string> logMaybe = Maybe.Some("entry9");

    var list = new List<string>();

    list.Add("entry1"); //List<T>.Add
    list.Add(logMaybe.ToAddIfHasValue()); //Our extension method
    list.Add("entry2"); //List<T>.Add
}

Take a look at a method called Add I defined in the ExtensionMethods class. Here is how it looks like:

public static void Add<T>(
    this ICollection<T> collection,
    AddIfHasValue<T> addIfHasValue)
{
    if (addIfHasValue.Maybe.TryGetValue(out var value))
    {
        collection.Add(value);
    }
}

The ToAddIfHasValue method allows us to wrap a Maybe object inside a special type, AddIfHasValue<T>. In the first version of Test16, the value returned by logMaybe.ToAddIfHasValue() is of type AddIfHasValue<string>. Therefore, our extension method (Add) is called to potentially add the value inside the Maybe to the list.

Note that we could have defined the Add method to work on Maybe<T> instead of AddIfHasValue<T>. The code in Test16 would look like this in this case:

static void Test16()
{
    Maybe<string> logMaybe = Maybe.Some("entry9");

    var list = new List<string>()
    {
        "entry1",
        logMaybe,
        "entry2"
    };
}

The problem would be that a reader of this code would expect that there are going to be three items in the list. Adding ToAddIfHasValue would make it easier for the reader to understand that the value will only be added if it exists.

Conclusion:

In this article, I talked about some nice methods that are designed to make it easier to deal with the Maybe type. I always find myself doing something over and over again with Maybe, and then I decide to add a special method to do it. I hope you will find these methods useful!

This article was technically reviewed by Damir Arh.

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Yacoub Massad is a software developer who works mainly with Microsoft technologies. Currently, he works at Zeva International where he uses C#, .NET, and other technologies to create eDiscovery solutions. He is interested in learning and writing about software design principles that aim at creating maintainable software. You can view his blog posts at criticalsoftwareblog.com


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

C# .NET BOOK

C# Book for Building Concepts and Interviews

Tags

JQUERY COOKBOOK

jQuery CookBook