Some functional types in C#
Update: I now have a project on GitHub containing this code at https://github.com/Richiban/Richiban.Func Check this repository for up to date code.
Continuing with my long-running train of thought in bringing some functional programming concepts to my C# projects, I have implemented a type called Optional, roughly equivalent to `Option` from F# or `Maybe a` from Haskell. If you’re not familiar with these concepts, then imagine Nullable from .NET, but applicable to all types (not just value types). The whole idea is that, rather than using a null reference of T to represent a missing value you use a new type–Optional–that can exist in one of two states: it either contains a value of T or does not.
This is a very important difference that I will explain over the course of this post. First, let’s look at the type definition itself. It’s quite long, so feel free to skim-read it at this point and refer back to it at later points in the post.
using System; using System.Collections; using System.Collections.Generic; namespace FunctionalCSharp { public struct Optional<T> : IEnumerable<T> { public static readonly Optional<T> None = new Optional<T>(); private readonly T _value; public readonly bool HasValue; public Optional(T value) { HasValue = value != null; _value = value; } public T Value { get { if (HasValue) { return _value; } throw new InvalidOperationException("The Optional does not have a value"); } } public static Optional<T> Create(T value) { return new Optional<T>(value); } public static implicit operator Optional<T>(T value) { return new Optional<T>(value); } public Optional<TResult> Select<TResult>(Func<T, TResult> func) { if (HasValue) return new Optional<TResult>(func(_value)); return new Optional<TResult>(); } public Optional<TResult> SelectMany<TResult>(Func<T, Optional<TResult>> func) { if (HasValue) return func(_value); return new Optional<TResult>(); } public Optional<TResult> SelectMany<TCollection, TResult>( Func<T, Optional<TCollection>> intermediateSelector, Func<T, TCollection, TResult> resultSelector) { if (HasValue) { var inner = intermediateSelector(_value); if (inner.HasValue) { return resultSelector(_value, inner._value); } } return new Optional<TResult>(); } public Optional<T> Where(Func<T, bool> func) { if (HasValue && func(_value)) { return this; } return new Optional<T>(); } public void Iter(Action<T> action) { if (HasValue) { action(_value); } } public T GetValueOrDefault(T defaultValue = default(T)) { if (HasValue) return _value; return defaultValue; } public TResult Match<TResult>(Func<TResult> none, Func<T, TResult> some) { if (HasValue) return some(_value); return none(); } public void Switch(Action none, Action<T> some) { if (HasValue) some(_value); none(); } public IEnumerator<T> GetEnumerator() { if (HasValue) yield return Value; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public override string ToString() { if (HasValue) { return _value.ToString(); } return "<none>"; } } }
There’s quite a lot going on here! First, since one of the reasons for the existence of this type is so that we don’t have to deal with null references, so this type is a struct. The methods can be grouped into three categories:
Convenience functions
Switch, Match, GetValueOrDefault
These are functions that are provided just to provide quick and easy access to the internal value of an Optional.
Match is a pattern-matching-style function to evaluate one block of code if the Optional has no value, or pass the value into another block of code if it has one. Use it like this:
var result = some.Match( none: () => "", some: value => value.ToString());
The Match method returns a value, unlike its counterpart Switch (this duality is necessary because in C# `void` is not a real type, so `Func` is not allowed).
GetValueOrDefault works in exactly the same way as its namesake on Nullable.
Implementation of IEnumerable
In a way an Optional can be thought of as a collection of T that contains either zero items or one item, which means it makes sense to implement IEnumerable.
Linq-style functions
Select, Where, SelectMany etc
These methods are implemented seperately even though we also implement IEnumerable. This is so that Linq operations return Optional instead of IEnumerable. Example:
var option = new Option<String>("Hello world"); var a = option.Select(opt => opt.ToUpper());
If we just implement IEnumerable and leave it at that then `a` is of type IEnumerable, but if we also implement `Select` ourselves then `a` is of type Optional.
Providing methods such as SelectMany is really cool, because it allows you to use Linq query syntax on your Optionals. E.g.
var opt1 = new Optional<string>("Hello world"); var opt2 = new Optional<Optional<string>>(opt1); var result = from value1 in opt1 from value2 in value1 select value2.ToUpper();
Why bother?
There are two principle benefits to introducing this class to your codebase, one is practical and one is ideological.
Imagine that your API (your type signatures) are your documentation (the legendary status of “self-documenting code”). To illustrate I will introduce a tiny domain in which we want to represent a Customer that may or may not have a telephone number. I will write the Customer class here but not the TelephoneNumber class (if you haven’t read my post on primitive obsession, please do).
public class Customer { public Customer(String name) { Name = name; } public String Name { get; private set; } public TelephoneNumber TelephoneNumber { get; set; } }
With the class above (pretend that we have real domain methods to set the telephone number instead of a public setter) we have a default constructor because it’s not mandatory to provide a telephone number. We will use the default value of `null` to represent a `Customer` without a telephone number. This is an approach with obvious problems, where it’s all too easy to call a method as innocuous as
var b = customer.TelephoneNumber.Trim();
and bang!, your application has crashed. Also it’s not clear–looking at the class from outside–which properties are allowed to be set to null and which are not. This leads us into the two advantages of Optional:
1. No more runtime exceptions! (in theory)
Since T and Optional are obviously different types, it shouldn’t be possible to end up with a `NullReferenceException` (although if you go calling `.Value` all over the place you are playing with fire and will get exceptions). The type system forces you, every time you work with one of these objects, to deal with the fact that there might not be a real value for you to use. This increases your program’s correctness and will lead to fewer bugs.
2. Your code is much more explicit about what can be omitted and what cannot
Now imagine that our domain object instead looked like this:
public class Customer { public Customer(String name, Optional<TelephoneNumber> telephoneNumber) { Name = name; TelephoneNumber = telephoneNumber; } public String Name { get; private set; } public Optional<TelephoneNumber> TelephoneNumber { get; set; } }
Now we are being much more explicit about the nature of our domain! Anyone looking at the public signature of our class can immediately see that a Customer may or may not have a telephone number, and in fact the code that caused a runtime failure earlier no longer compiles. We would have to substitute it for something else that explicitly checks for None, such as:
var b = customer.TelephoneNumber.Match(none: () => "", some: s => s.Trim());
I hope this has been a convincing argument for the introduction of another functional programming concept into C#. It’s yet another illustration (I feel) for why becoming a polyglot programmer can increase your skills even if you only really develop in one language for your day job.