Dmitri Nesteruk’s recent post Chained null checks and the Maybe monad struck a chord with me as I had messed about with something similar for performing a visitor-esque operation. I’ve glanced at a few posts about monads in the past however this is the first time I’ve had a proper look at one of them.
The purpose of the Maybe monad is essentially to remove the need for null reference checking. If you try to perform some function on an object which turns out to be null you might get a null reference exception. If, however, you perform the function on a Maybe then if the object is null the function is never called. It’s particularly useful if you’re performing a long chain of functions on an object, any of which may return null. In these cases when the null is encountered the remainder of the chain is skipped resulting in more robust, better performing code.
The implementations in .NET that I could find vary quite widely:
- Maybe project
- Maybe monad in Lokad shared libraries
- M<’a> Lib
- Zack Owens’ version
- Random one from Stack Overflow (Judah Himango)
One aspect shared by most of these implementations, and which was pointed out in the comments of Dmitri’s post, is that they still end up doing all the null checking, it’s just hidden away. They are treating the “nothing” state as a value, effectively just creating a Nullable<T> which wraps reference types and then checking the HasValue at the beginning of each method call. I think a more elegant solution to this is to use the Null Object pattern.
A Null Object is a special inert type derived from our real class or a common base class. Each method is overridden by a version which has no effect. By wrapping any non-null objects we encounter in an instance of our real type and any nulls in an instance of our inert type we can continually call the methods of these types without fear of null reference exceptions occurring. Moreover, once we receive our inert type from one of the method calls we’re calling the methods on that type so we don’t need null checks at the beginning of our methods as the implementations we’re calling will have no effect.
Example
// Simple testing class
class Node
{
public int Number { get; set; }
public Node Parent { get; set; }
}
// Arrange
Node node = new Node
{
Number = 1,
Parent = new Node
{
Number = 2,
Parent = new Node
{
Number = 3
}
}
};
// Act
var third = node.Maybe()
.Apply(n => n.Parent)
.Apply(n => n.Parent)
.Return();
// Assert
Assert.IsNotNull(third);
Assert.AreEqual(3, third.Number);
Here we've got a simple test class and object graph and our code is trying to return the grandparent of node. First we use the Maybe extension method to create the Maybe object after this we're calling methods on the Maybe object itself. The Apply method behaves like a Map method and applies the supplied Func to the subject of the Maybe, returning its result as a new Maybe object. The Return then unwraps the Maybe and returns the subject object if there is one. If any of the methods called on the Maybe object fail we'll end up with a null coming back from Return.
Implementation
The basic structure is an abstract Maybe class with two derived classes; ActualMaybe which contains the real implementation and NothingMaybe which is the Null Object type. The implicit operator on Maybe is where any null is handled.
public abstract class Maybe<T> where T : class
{
public static readonly Maybe<T> Nothing = new NothingMaybe<T>();
public static implicit operator Maybe<T>(T t)
{
return t == null ? Nothing : new ActualMaybe<T>(t);
}
}
class ActualMaybe<T> : Maybe<T> where T : class
{
readonly T _t;
public ActualMaybe(T t)
{
if (t == null) throw new ArgumentNullException("t");
_t = t;
}
}
class NothingMaybe<T> : Maybe<T> where T : class
{
}
The implementation for Apply is as follows:
// Maybe<T>
public abstract Maybe<TResult> Apply<TResult>(Func<T, TResult> func) where TResult : class;
// ActualMaybe<T>
public override Maybe<TResult> Apply<TResult>(Func<T, TResult> func)
{
return func(_t);
}
// NothingMaybe<T>
public override Maybe<TResult> Apply<TResult>(Func<T, TResult> func)
{
return Maybe<TResult>.Nothing;
}
Apply takes the map function func which operates on the type T and returns some other type TResult. Apply itself returns the Maybe of TResult.
The ActualMaybe implementation simply calls func passing _t, which is the contained object, and returns the result of func. There is more going on here though; first _t can't be null because of the check in the ActualMaybe constructor so we don't need a null check, second we return whatever comes out of func but because the method returns a Maybe of TResult the implicit cast takes place and any nul coming out of func is replaced.
The NothingMaybe implementation ignores func altogether and just returns a NothingMaybe of TResult using the static readonly Nothing field on Maybe<T>.
The ActualMaybe implementation of Return returns _t while the NothingMaybe implementation always returns null.
I’ve implemented a couple of other useful methods including Do(Action<T>), If(Predicate<T>), Cast<TResult>() and AsEnumerable() as well as several overloads.
Possibilities
I think this Null Object approach could be combined with the Visitor pattern to achieve some extensibility although I’m not entirely sure how it would work or whether it would even be necessary.
Another possible extension is some kind of Collect method which would allow you to cherry pick particular objects from a graph and then would return an IEnumerable over just those objects at the end.
Code
I’ve put the code up on Github here:
http://github.com/dezfowler/Monads