I’ve been interested to start using the Null Object / Option Type approach for replacing my old habits of null-checking multiple parts of my code. But, from the many examples I saw out there, it seems it’s a pattern used in conjunction with generics.
Great for C#, Java, Haxe and a bunch of others I’m sure.
NOT so great for ActionScript 3.0 (which is what I was intending to use it for).
So, am I under the wrong impression that this won’t be possible / worthwhile in a language that doesn’t support Generics? Or is there a way to pull it off without them?
NOTE: It seems the “Null Object” pattern shown in this PatternCraft tutorial doesn’t quite resemble the way Option Type was described in some of the other discussions in this stackexchange site. His way looks more like a factory class that spits out a Null??? derivative of some base-class. I was hoping there could be a way to create Null-Objects without needing to write a dedicated class for every single case scenarios where a null-check would typically be used.
EDIT: My apologies, I didn’t realize “Null Object” and “Option Type” were two completely different beasts. For the sake of keeping the answers and feedback relevant to the question though, I only changed the title (was originally How to use Null Object / Option Type Pattern in a language that doesn't support generics?
).
5
There is major difference between Null Object pattern and Option types.
Null Object is pure OOP (thus no generics) pattern, where one object implements abstract class / interface in such a way, that calling the method through the interface is same as not calling the method at all. That means the caller has no idea that type of instance is a Null object. So these two cases are equivalent:
// without null object
IInterface obj = null;
if(obj != null)
obj.method()
// with null object
IInterface obj = new NullObject();
obj.method()
Option types on the other hand are type-safe way to ensure the caller of the method knows that there is possibility that variable might contain “no valid value”. This gives compiler power to either warn or error when programmer tries to call method on variable, that might contain null
.
[PSEUDOCODE (that looks like C#)]
// case A
Option<IInterface> obj = getObject();
obj.Method() // ERROR, possible "Null reference exception"
// case B
Option<IInterface> obj = getObject();
if (obj.HasValue)
obj.Method() // OK, compiler knows that obj has valid value thanks to the if
But I believe option types don’t make much sense in language, that doesn’t have them as language feature (eg. most OOP languages), because compiler is not implemented as to detect such things as the first case (unless you use some kind of static code correctness checking). So you gain nothing against normal null
check. Null object pattern on the other hand is natural in all OOP languages. Even those without generics.
Good example of OOP language having support of Option type is C#’ Nullable. You know value types always have valid value and Nullable allows you to specify cases where variable might not contain valid value. But this is only for value types.
6
Option
is isomorphic to a collection which can only ever be empty or contain a single element. So, you implement it the same way you would any other collection.
If you can implement a list, set, map, tree, heap, stack, array, trie, treap etc. in your language, then you can implement Option
in exactly the same way.
See AS3Commons Collections for a comprehensive collections framework implemented in ActionScript. (Note: it doesn’t seem to be as powerful as Scala’s, but it can at least compare favorably with Java’s.) In essence, without parametric polymorphism, you lose type-safety, the same way you do with Java 1.1, .NET 1.0, C, Pascal, Modula, etc.
The purpose of option types is to let the compiler know exactly where a value may be empty in a language where null simply does not exist (typical for any language that supports algebraic data types). This way, the compiler can give compiler-time errors if you forget to check for emptyness. Here’s an example in Rust, an imperative language with a C-like syntax:
let my_number : Option<int> = read_number();
return 2 * my_number; // compile error
This won’t compile because my_number
does not contain an int
but an Option<int>
(i.e. an int
that may be empty). The compiler does not allow multiplication on Option<int>
.
Therefore, it is necessary to pattern match my_number
in order to extract its value:
let my_number : Option<int> = read_number();
match (my_number) {
Some(my_actual_number) =>
return Some(2 * my_actual_number); // ok
None ->
return None; // fallback if it's empty
The pattern matching forces the programmer to handle every possible scenario: in this case, the None
case. This is how it prevents the typical “forgot to check for null” problem.
However, in a language where null is pervasive, option types aren’t nearly as useful since everything can already be null anyway. Therefore the compiler can’t really help you (since inferring the nullability would require solving the halting problem).
The null object pattern not the same thing, but it is a related concept.
In languages that have first-class support for option types, one is permitted to apply an operation to the internal object inside the option type, if it exists. It serves as a shortcut for the common activity of “do X to the object, but if it’s empty then forget about it”. This process is typically called “mapping” and this operation can be defined for all option types.
The map operation takes an option object (Option<T>
), as well as a function that can act on the internal object (T -> R
), and produces the result of the function wrapped in an option object (Option<R>
).
In fact, the example above can be rewritten much more succinctly using a map combined with an anonymous function:
let my_number : Option<int> = read_number();
return my_number.map(
// use anonymous function to specify what to do
|my_actual_number| 2 * my_actual_number
);
Map can be thought of as a recipe that:
- If the option object is not empty, then apply the provided function and return the result in a
Some(...)
. - If the option object is in fact empty, then do nothing and just return
None
.
In the null object pattern, one uses a “null” class that possesses same methods as the non-null class (they could be siblings in the inheritance hierarchy), but arranged in such a way that the operations on the “null” class simply do nothing. Hence, the null object pattern is similar in that it accomplishes this same goal, albeit in a different, more object-oriented/duck-typing way.
1