The Either
type, also called Choice
in F# parlance, is a way of representing a value that can be either one of two types. This can be extremely useful. For example, retrieving a date of birth from a textbox could be expressed as Either<ParseException, DateTime>
. In other words, the result is a value that is either a valid DateTime
, or is a ParseException
.
Being a hasty introduction this post is not going to do justice to how useful this type is, but hopefully some of its goodness will shine through despite my rambling prose. :)
Implementation
In .NET we don’t need to implement this ourselves. We can use F#’s Choice
type from any .NET language including C# using the same steps as for Option
, or use Either
from the Sasa library1. I think it’s useful to look at how we could implement this ourselves though, so here goes…
It’s a bit clumsy to represent in C#, but here’s one implementation:
F# makes things much easier:
Both implementations show two main points about Either
:
- It has two generic parameters
- We can construct an
Either
that has a value of the first type, or a value of the second type, but not both.
Accessing either value
We’ve seen the basic Either
structure, but so far have no way to use it. Everything we need to do with Either
we can do by adding this function2:
If we have an Either<ParseException, DateTime>
this will let us handle both cases.
We can implement a number of helpful functions that make dealing with Either
values really convenient.
Why?
Because we are accurately communicating to people that a method can return two different types of results, or takes a value as an argument that can be one of two types. Importantly, we’re also telling the compiler exactly what the possible values are, so it will tell us if we’re not handling any cases.
Fold
and similar functions to use either value in very convenient, low friction ways. The only times it causes me any pain is when I’ve completely overlooked a possibility in my code, and the compiler keeps me honest and makes me handle the case. I much prefer this to getting a bug report or having silently failing code.
Sticking with our Either<ParseException, DateTime>
example, there is no way we can forget to handle the possibility our code could fail to parse out a DateTime
. If we used a traditional exception we’re relying on there being documentation, people reading that documentation, and people never making a mistaek.3
An unexceptional example
Using Either
to represent operations that can potentially fail is a convenient example, but we don’t need to restrict its use to those cases. One example that came up on the NSubstitute mailing list was dealing with values that could be partially or fully loaded. Rather than relying on a boolean flag to indicate a load state (and hoping everyone remembers to check it before accessing unloaded fields), we can use something like Either<PersonSummary, Person>
.
If we need to display the person’s name, regardless of whether the Person
has been fully loaded, we can easily extract the information we need:
At some point we need to ensure the full entity is loaded, which we can express like this:
Conclusion
The Either
type lets us represent a value that can be one of two possible types. By using this as a return type or argument type, we communicate to readers and the compiler that there are two possibilities, which ensures we do not accidentally forget to handle particular cases. Using Fold
and related functions gives us really convenient, low-friction ways of using, transforming and combining these values. I can’t really see a downside to using this type (please comment if you can. Normally not seeing negatives to something means I don’t understand it sufficiently).
Don’t forget, we can start using all this from C# right away by referencing FSharp.Core and FSharpx or by grabbing the Sasa library from Nuget.
Everything you can do with a type can be expressed by its catamorphism.↩
It’s also a much more convenient mechanism than Java’s checked exceptions. We can pass around and compose
Either
values without requiring special syntax liketry/catch
.↩