Sunday, November 06, 2011

C# : The null object pattern and the poor man's Maybe Monad

Lots of people have re-invented the Maybe monad in C#, as a simple search will show, usually as a way of bypassing null checking if statements and to be able to write code like

return Music.GetCompany("4ad.com")
.IfNotNull(company => company.GetBand("Pixies"))
.IfNotNull(band => band.GetMember("David"))
.IfNotNull(member => member.Role);
view raw gistfile1.cs hosted with ❤ by GitHub

But we have the tools given to us already if we observe that null has no type -- as we can see when

var x = new string[1]; // default null value
var n = x.OfType<string>().Count();
Console.WriteLine("Length = {0}", n);
view raw gistfile1.cs hosted with ❤ by GitHub

reports a length of zero!

So our Some<T> is just a length-1 IEnumerable<T>; and None<T> an empty one -- the null object pattern, in fact.

For the price of having to specify types at each stage -- as we would anyway have had to in the past for declaring intermediate the values to test against null -- we can use existing methods to finesse the null check. Returning to the borrowed example, it would be

return new[] { Music.GetCompany("4ad.com") }
.OfType<RecordCompany>().Select(company => company.GetBand("Pixies"))
.OfType<Band>().Select(band => band.GetMember("David"))
.OfType<Member>().Select(member => member.Role);
view raw gistfile1.cs hosted with ❤ by GitHub

The monadic return monad extraction operation is just FirstOrDefault(); which is fine for reference types. Value types are different in any case as we wouldn't be null checking those -- as the later steps in a chain though, stopping at the filtering to the value type and making an Any test may be preferred.

Looking at value types, switching to this example, the code looks like:

public static IEnumerable<int> Div(this int numerator, int denominator)
{
//return denominator == 0
// ? Enumerable.Empty<int>()
// : new[] { numerator/denominator };
if (denominator != 0) yield return numerator/denominator;
}
public static IEnumerable<int> DoSomeDivision(int denominator)
{
return from a in 12.Div(denominator)
from b in a.Div(2)
select b;
}
...
var result =
from a in new[] { "Hello World!" }
from b in DoSomeDivision(2)
from c in new[] { new DateTime(2010, 1, 14) }
select (a + " " + b.ToString() + " " + c.ToShortDateString());
Console.WriteLine(result.Any() ? result.First() : "Nothing");
view raw gistfile1.cs hosted with ❤ by GitHub

which leaks a little bit in that we have to do the monadic return monad extraction by hand at the end, but otherwise behaves exactly as the explicit monad implementation.

1 comment :

Mauricio Scheffer said...

Interestingly, I posted exactly about this just around a week ago: http://bugsquash.blogspot.com/2011/10/poor-man-option-type-in-c.html
Just a couple of minor corrections: monadic return is the lifting of pure values into the monad, so it's actually the array constructor, not FirstOrDefault() (i.e a function T -> M instead of M -> T).
First() is better here than FirstOrDefault(), as it makes more patent the partiality of the function... just like Haskell's fromJust and F#/OCaml's Option.get , it fails when there is no value.