Some optional, functional goodness in C#

For this post I wanted to show you a small yet extremely useful bit of functional programming that you can apply right away to your current C# (or VB.NET) project. While there’s solid theory underpinning it, we won’t need any of that to be able to apply it, so we’ll jump straight to the practice. (I love the theory, so this is quite a sacrifice I’m making for you! ;)) As an added bonus, we’ll also get a good stepping stone for starting to apply more of these practical FP ideas in our everyday code.

Are you null? If not then do stuff!

Dealing with a null is a fact of life in our C# projects. It is not unusual to end up with code that has one or more null checks. Let’s start with a small example:

public Order GetOrderWhilePeckish(IWaiter waiter) {
    var menu = waiter.GetCurrentMenu();
    if (menu == null) {
        return null;
    }
    return new Order(menu.Everything());
}

Here we get the menu from a waiter. If there is no menu available, then we don’t place an order. Otherwise, we order everything on the menu.

There are two issues with this. First is the noise; the guard clause obscures much of what the method is doing. This gets worse as the number of nullable references increases (we can end up with an explosion of guard clauses, or arrow code).

The second issue is that we are using null to indicate that there is no current menu, which is a value of some significance. We can also get null because we haven’t initialised a reference, which is generally due to a mistake rather than any particular significance. It would be nice to separate these cases, so that GetCurrentMenu() can return either no menu or some menu.

Let’s try and address these problems.

Null, or empty?

Rather than treating GetCurrentMenu() as something that returns a Menu object, let’s treat its output as a list of menus. This list is either going to contain a single Menu, or be empty. But we’ll make sure it never returns null.

private IEnumerable<Menu> ToList(Menu menu) {
    // Produce a single item or empty list; never null.
    if (menu != null) yield return menu;
}

public Order GetOrderWhilePeckish(IWaiter waiter) {
    var menuOrNothing = ToList(waiter.GetCurrentMenu());
    return menuOrNothing
            .Select(menu => new Order(menu.Everything()))
            .SingleOrDefault();
}

We’re now dealing with a list of menus (that just happens to contain 0 or 1 items), so we can use all the standard LINQ extension methods like Select to transform each item in our list into the required order. Or if no menu is available, we get an empty list of orders back. To complete this step of the refactor, we need to convert our list back into a nullable reference to an Order, which we do using SingleOrDefault(). This will return our single order or null.

Note: You might be thinking it would be better to update our GetOrderWhilePeckish method to return a list of Order objects to indicate that it too can return no orders. This is a good line of thought! At this point though we’re just making a small refactor; we don’t have to change our callers, including those in our tests.

And because we’re all LINQified now, we can also use the LINQ query comprehension syntax:

public Order GetOrderWhilePeckish(IWaiter waiter) {
    var orderOrNothing =
        from menu in ToList(waiter.GetCurrentMenu())
        select new Order(menu.Everything());
    return orderOrNothing.SingleOrDefault();
}

Intermission

It may be a bit hard to see the benefit of this refactor at this point. We’ve eliminated a null check, but the code isn’t significantly simpler. We’ll get to more complex examples in a minute, but first let’s formalise the concept of “none or one”, rather that commandeering lists for that purpose.

A little nuget

It turns out there is already a type in .NET to represent “none or one”, but to use it effectively we’ll want to install a nuget package or two: FSharp.Core and FSharpx.Core (you can also use your existing F# installation instead of the FSharp.Core package).

PM> Install-Package FSharp.Core
PM> Install-Package FSharpx.Core

Then we just need using FSharpx; at the top of our file, and we’re ready to go.

Aside: Alternatively, we can always write our own option type without much trouble. Let me know if you’d like me to go through an implementation.

Another option

The type we want to represent our “none or one” value is called option from F#, and it is exposed to C# as FSharpOption. The FSharpx library gives us some convenient functions for working with this type. The only changes we need to make to our previous code is to call the ToFSharpOption() extension method instead of ToList(), and replace SingleOrDefault() with GetOrDefault().

public Order GetOrderWhilePeckish(IWaiter waiter) {
    var orderOrNothing =
        from menu in waiter.GetCurrentMenu().ToFSharpOption()
        select new Order(menu.Everything());
    return orderOrNothing.GetOrDefault();
}
Note: Calling ToFSharpOption() will work fine on a null reference because it is an extension method; it’s syntactic sugar for calling FSharpOption.ToFSharpOption(menu), just like our ToList(menu) call from before.

Logically this works just like our list example. The only difference is we’re using the FSharpOption<T> type instead of IEnumerable<T>.

Different type, same LINQ goodness

Let’s look at a more complicated example. Our restaurant patron is down on their luck, but is desperate for a shot of bacon. They’ll see if the waiter has a menu, and if so then check it for the cheapest item. If that item contains bacon, then we’ll order it. In all other cases our patron will not place an order.

public Order GetOrderForBaconFanWhoIsDownOnTheirLuck(IWaiter waiter) {
    var currentMenu = waiter.GetCurrentMenu();
    if (currentMenu == null) {
        return null;
    }
    var cheapestItem = currentMenu.CheapestItem();
    if (cheapestItem == null) {
        return null;
    }
    return cheapestItem.Contains("Bacon") ? new Order(cheapestItem) : null;
}

We can convert this from bottom-to-top in small steps, but I’ve found it easiest to start at the top and work down through the whole thing.

public Order GetOrderForBaconFanWhoIsDownOnTheirLuck(IWaiter waiter) {
    var maybeOrder =
        from currentMenu in waiter.GetCurrentMenu().ToFSharpOption()
        from cheapestItem in currentMenu.CheapestItem().ToFSharpOption()
        where cheapestItem.Contains("Bacon")
        select new Order(cheapestItem);
    return maybeOrder.GetOrDefault();
}

This reads much like our initial description of this scenario: from current menu get cheapest item and place and order if it contains bacon. If any of these is null no order will be placed.

The FSharpOption type supports all the standard LINQ: Select, SelectMany, Where, let-bindings etc, so there is nothing new to learn, we’re just using options instead of lists.

More benefits

So far we’ve focussed on refactoring the insides of methods, rather than changing signatures. This has simplified our code a bit and also made our intentions more explicit by calling out places where we expect null.

We can start getting a lot more benefit by pushing options into interfaces. Rather than waiter.GetCurrentMenu() returning null or a menu, we can make it return FSharpOption<Menu>. Callers then have to explicitly deal with the empty case, and the compiler will keep them honest.

Having FSharpOption<T> in our APIs also clears up our calling code. We no longer need to use .ToFSharpOption() or GetOrDefault().

The other thing we gain is the ability to compose these types to perform some interesting operations. For example, say we want to check whether either of two properties is present, we can compose them using first.OrElse(second), then use HasValue() to check if either are non-empty, or GetOrElse(blah) to use access the first value present (defaulting to blah).

There are lots of handy functions on FSharpOption, but for now I should just note that we can construct instances of options using myObj.Some() for non-empty, and FSharpOption<T>.None for empty values.

Gateway to functional programming

As we’ve seen, options can be introduced to clean up some null checks with zero impact on the rest of our code. As we become more comfortable with the ways of using options we can start using them in private fields, then in public interfaces, which starts making our code safer and more intention-revealing. After that, we can start trying other types that FSharp and FSharpx provide, such as FSharpChoice.

This gives us a great, risk-free way of gradually becoming accustomed to some functional programming techniques. In turn this familiarity gives us new ways of thinking about problems which will undoubtedly come in handy, even if we don’t end up using FP all the time.

Comments