5

So, it's pretty well known that the infamous NullReferenceException is the most common exception in software products. I've been reading some articles, and found myself with the Optional approach.

Its aim is to create some kind of encapsulation around a nullable value

public sealed class Optional<T> where T : class {

    private T value;

    private Optional(T value) {
        this.value = value;
    }

    //Used to create an empty container
    public static Optional<T> Empty() {
        return new Optional(null);
    }

    //Used to create a container with a non-null value
    public static Optional<T> For(T value) {
        return new Optional(value);
    }

    //Used to check if the container holds a non-null value
    public bool IsPresent {
        get { return value != null; }
    }

    //Retrieves the non-null value
    public T Value {
        get { return value; }
    }
}

Afterwards, the now optional value can be returned like this:

public Optional<ICustomer> FindCustomerByName(string name)
{
    ICustomer customer = null;

    // Code to find the customer in database

    if(customer != null) {
        return Optional.Of(customer);
    } else {
        return Optional.Empty();
    }
}

And handled like this:

Optional<ICustomer> optionalCustomer = repository.FindCustomerByName("Matt");

if(optionalCustomer.IsPresent) {
     ICustomer foundCustomer = optionalCustomer.Value;
     Console.WriteLine("Customer found: " + customer.ToString());
} else {
     Console.WriteLine("Customer not found");
}

I don't see any improvement, just shifted complexity. The programmer must remember to check if a value IsPresent, in the same way he must remember to check if a value != null.

And if he forgets, he would get a NullReferenceException on both approaches.

What am I missing? What advantages (if any) does the Optional pattern provide over something like Nullable<T> and the null coalescing operator?

Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
Matias Cicero
  • 25,439
  • 13
  • 82
  • 154
  • 1
    The advantage is readability. If you feel the code is better understood, then go with that method. Sometimes, null checks feel like the developer was scared and wrapped it up in a check just in case, however if you have something that is clearly intentional Optional .. customer.IsPresent, it might be easier to follow. – tbddeveloper Jul 30 '14 at 18:07
  • 2
    This code is like half Java and half C#, and would have a number of compile errors in either language... – Servy Jul 30 '14 at 18:09
  • Well, for a practical example: don't we all use this on a daily basis with [Nullable value types](http://msdn.microsoft.com/en-us/library/1t3y8s4s.aspx)? – Juliën Jul 30 '14 at 18:10
  • 6
    I would respectfully disagree with @Hammerstein. I find the wrapper far less readable than simply using `!= null` directly. For finding this kind of error, I would be more inclined to use a tool such as ReSharper which can perform code analysis to find the issue. This allows decorating methods that may return `null` with an attribute indicating such. I'm sure there's other tools which work in a similar manner. Another option is the `Try...` approach used on, for example, `Dictionary.TryGetValue`. – Dark Falcon Jul 30 '14 at 18:12
  • 2
    There is no advantage to what you've presented here - the whole point of using `Option`/`Maybe` is that you don't need to check for the presence of a value. You do this by defining `return\map\bind` operators and then requiring that clients have to provide a default value if they want to try unpack the inner value. You also wouldn't restrict `T` to reference types and instead treat all possible types for `T` uniformly. – Lee Jul 30 '14 at 18:15
  • 1
    See [What is a NullReferenceException and How do I Fix It?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) and actually _fix_ the problems instead of wasting time on patterns meant to hide it. – John Saunders Jul 30 '14 at 18:19
  • 1
    This `Optional` looks like `Nullable` – Wiktor Zychla Jul 30 '14 at 18:20
  • 2
    LOL java.... I've been coding in C# for years and the number of `NullReferenceException`s I got can be counted with a right hand's fingers. Completely agree with @JohnSaunders, find and fix the damn bug instead of creating an endless pile of abstractions and patterns in order to hide it. – Federico Berasategui Jul 30 '14 at 18:22
  • It seems like you're just checking for null by creating a class that checks for null. And now do you have to check if your Optional instance is null? – Will Eddins Jul 30 '14 at 18:40
  • .NET 6 will, I believe, be getting the Safe Navigation Operator. That'll help with this as well. – Colin DeClue Jul 30 '14 at 19:00
  • @ColinDeClue: you mean C# 6, not .NET 6. – John Saunders Jul 30 '14 at 19:02
  • @JohnSaunders: Oops, yes. My sentence didn't make any sense. – Colin DeClue Jul 30 '14 at 19:08

4 Answers4

11

Free your mind

If you think of Option as Nullable by a different name then you are absolutely correct - Option is simply Nullable for reference types.

The Option pattern makes more sense if you view it as a monad or as a specialized collection that contain either one or zero values.

Option as a collection

Consider a simple foreach loop with a list that cannot be null:

public void DoWork<T>(List<T> someList) {
    foreach (var el in someList) {
        Console.WriteLine(el);
    }
}

If you pass an empty list to DoWork, nothing happens:

DoWork(new List<int>());

If you pass a list with one or more elements in it, work happens:

DoWork(new List<int>(1));
// 1

Let's alias the empty list to None and the list with one entry in it to Some:

var None = new List<int>();
var Some = new List(1);

We can pass these variables to DoWork and we get the same behavior as before:

DoWork(None);

DoWork(Some);
// 1

Of course, we can also use LINQ extension methods:

Some.Where(x => x > 0).Select(x => x * 2);
// List(2)
// Some -> Transform Function(s) -> another Some

None.Where(x => x > 0).Select(x => x * 2);
// List()
// None -> None

Some.Where(x => x > 100).Select(x => x * 2);
// List() aka None
// Some -> A Transform that eliminates the element -> None

Interesting side note: LINQ is monadic.

Wait, what just happened?

By wrapping the value that we want inside a list we were suddenly able to only apply an operation to the value if we actually had a value in the first place!

Extending Optional

With that consideration in mind, let's add a few methods to Optional to let us work with it as if it were a collection (alternately, we could make it a specialized version of IEnumerable that only allows one entry):

// map makes it easy to work with pure functions
public Optional<TOut> Map<TIn, TOut>(Func<TIn, TOut> f) where TIn : T {
    return IsPresent ? Optional.For(f(value)) : Empty();
}

// foreach is for side-effects
public Optional<T> Foreach(Action<T> f) {
    if (IsPresent) f(value);
    return this;
}

// getOrElse for defaults
public T GetOrElse(Func<T> f) {
    return IsPresent ? value : f();
}

public T GetOrElse(T defaultValue) { return IsPresent ? value: defaultValue; }

// orElse for taking actions when dealing with `None`
public void OrElse(Action<T> f) { if (!IsPresent) f(); }

Then your code becomes:

Optional<ICustomer> optionalCustomer = repository.FindCustomerByName("Matt");

optionalCustomer
    .Foreach(customer =>
        Console.WriteLine("Customer found: " + customer.ToString()))
    .OrElse(() => Console.WriteLine("Customer not found"));

Not much savings there, right? And two more anonymous functions - so why would we do this? Because, just like LINQ, it enables us to set up a chain of behavior that only executes as long as we have the input that we need. For example:

optionalCustomer
    .Map(predictCustomerBehavior)
    .Map(chooseIncentiveBasedOnPredictedBehavior)
    .Foreach(scheduleIncentiveMessage);

Each of these actions (predictCustomerBehavior, chooseIncentiveBasedOnPredictedBehavior, scheduleIncentiveMessage) is expensive - but they will only happen if we have a customer to begin with!

It gets better though - after some study we realize that we cannot always predict customer behavior. So we change the signature of predictCustomerBehavior to return an Optional<CustomerBehaviorPrediction> and change our second Map call in the chain to FlatMap:

optionalCustomer
    .FlatMap(predictCustomerBehavior)
    .Map(chooseIncentiveBasedOnPredictedBehavior)
    .Foreach(scheduleIncentiveMessage);

which is defined as:

public Optional<TOut> FlatMap<TIn, TOut>(Func<TIn, Optional<TOut>> f) where TIn : T {
    var Optional<Optional<TOut>> result = Map(f)
    return result.IsPresent ? result.value : Empty();
}

This starts to look a lot like LINQ (FlatMap -> Flatten, for example).

Further possible refinements

In order to get more utility out of Optional we should really make it implement IEnumerable. Additionally, we can take advantage of polymorphism and create two sub-types of Optional, Some and None to represent the full list and the empty list case. Then our methods can drop the IsPresent checks, making them easier to read.

TL;DR

The advantages of LINQ for expensive operations are obvious:

someList
    .Where(cheapOp1)
    .SkipWhile(cheapOp2)
    .GroupBy(expensiveOp)
    .Select(expensiveProjection);

Optional, when viewed as a collection of one or zero values provides a similar benefit (and there's no reason it couldn't implement IEnumerable so that LINQ methods would work on it as well):

someOptional
    .FlatMap(expensiveOp1)
    .Filter(expensiveOp2)
    .GetOrElse(generateDefaultValue);

Further suggested reading

Community
  • 1
  • 1
Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
1

Did you mean: Null Object pattern

The article linked to me in the comments contains a conclusion section explained this programming tool.

... The purpose of Optional is not to replace every single null reference in your codebase but rather to help design better APIs in which—just by reading the signature of a method—users can tell whether to expect an optional value. .... deal with the absence of a value; as a result, you protect your code against unintended null pointer exceptions.

Anyway, let it crash and find the reason. If you do not want endlessly embedded if statements than use an implementation pattern Guard Clause pattern, which says the following:

While programs have a main flow, some situations require deviations from the main flow. The guard clause is a way to express simple and local exceptional situations with purely local consequences.

Balázs Kreith
  • 141
  • 2
  • 8
  • No this is different than the Null Object Pattern which is a way to provide a default value. See this for more info http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html – dkatzel Jul 30 '14 at 19:01
  • Please edit your answer to include the relevant advantages listed in your links. Link-only answers are a no-no. – Austin Mullins Jul 30 '14 at 19:10
1

it would probally make more sense if you used something like this

interface ICustomer {

    String name { get; }
}

public class OptionalCustomer : ICustomer {

     public OptionalCustomer (ICustomer value) {
          this.value = value;
     }
     public static OptionalCustomer Empty() {
          return new OptionalCustomer(null);
     }

     ICustomer value;

     public String name { get { 
         if (value == null ) {
             return "No customer found";
         }
         return value.Name;
      }
    }

}

now if your pass an "empty" optional customer object you can still call the .Name property (without getting nullpointers)

Batavia
  • 2,497
  • 14
  • 16
1

The advantage of Optional is you know if something may not exist.

The problem with many types of queries that return a null is that that could mean 2 things:

  1. The query didn't return a result
  2. The query returned a result whose value was null.

I know you're asking specifically about C# but Java just introduced Optionals in Java 8 so there are a lot of articles about it so I'll use Java as an example. but it's completely the same idea as in C#:

Consider the Java Map.get(key) method

Object value = map.get(key);
if(value ==null){
    //is there an entry in the map key =>null or does key not exist?
}

to get around that you have to have an additional method containsKey( k)

With optional, you only need one method

 Optional<Object> result = map.get(key);
 if(result.isPresent()){
    Object value = result.get();
    //if value is null, then we know that key =>null
 }

More info see this Java article : http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

dkatzel
  • 31,188
  • 3
  • 63
  • 67