This is another part of our series on Programming Principles with another functional programming method: the concept of Optionals.
Functional programming prefers to use pure functions, avoid mutable data, and avoid side-effects, choosing methods that are more predictable, precise, and both easier to read and test.
But not everyone knows about Optionals. So, let’s take a closer look at what issues may come up, why they happen, and how you can use Optionals (or other alternatives) to write more stable, predictable, and usable code.
The problem with null or undefined
- A number
- A string
- Null (something that doesn’t have a value)
- Undefined (a variable you’ve declared but haven’t assigned anything to yet)
Null and undefined have several differences and similarities.
Null or undefined are usually used if you don’t have a value ready to assign yet, or if you explicitly want a variable to not have a value. This might happen in instances where you’re working with user input. Maybe you have a form with an input field where users can add their name. Until they’ve typed something and hit submit, any variable linked to that value would be null or undefined. Or if they skip over a field in the form and hit submit, that variable would remain null or undefined. In places where your code tries to use that value, you first need to make sure it’s an actual value by asking “is this value null or undefined”? If it has a value, your code can continue on. If not, you need to handle the fact that the value is missing. Depending on what your code does, this can result in a lot of if statements and branching.
Here’s an example where the value is undefined:
alert(testVar); //shows undefined
alert(typeof testVar); //shows undefined
Here’s an example where the value is null:
var testVar = null;
alert(testVar); //shows null
alert(typeof testVar); //shows object
These values are problematic for a few reasons.
First of all, having to repeat that null or undefined check every time gets pretty annoying. Most programming languages have one keyword that means “this doesn’t have a value yet”. Usually, they’ll have something like null or undefined, not both. Having to check both these values repeatedly can add bloat and complexity to your code. It also means more mental overhead when programming - you have to recognise where null and undefined might be problematic, and remember to add in the necessary checks to ensure they don’t break anything.
But most importantly, null and undefined can hide bugs or make them harder to figure out. You may need to track the value of a variable from its creation until the point the bug happens to figure out when the value changes.
Zero is falsey.
One is truthy.
An array is falsey.
An object is truthy.
Both null and undefined are falsy.
…it doesn’t make much sense.
Overall, this is very un-functional programming, as there’s no way to be sure about what the outputs are going to be with constantly doing extra checks that can easily be forgotten.
That’s where Optionals come in
To replace null or undefined and avoid the mess that comes with it, we use Optionals as a replacement.
What are Optionals?
An Optional is a data structure that has an optional value. More strictly, an Optional can either have a value or nothing - it must be one or the other. This forces you to handle both cases - you can’t forget like it’s so easy to do with null and undefined values.
That means instead of making a variable that starts out as “undefined”, you make a variable and say it’s “option none”. Then later on, once you get a value for the variable, you make it an “option some of the value” which wraps it up nicely.
// Tiny’s Optional.from() returns a none if given null or undefined, else returns a some of the value
// isSome() returns true if the variable is a some, or false if it is a none
// getOr(fallback) returns the value if the variable is a some, or the fallback argument if it is a none
var foo = Optional.some(“hello”); // foo is a some of the value “hello”
foo.isSome(); // returns true
foo.getOr(“fallbackValue”); // returns “hello”
var bar = Optional.none(); // bar is a none
bar.isSome(); // returns false
bar.getOr(“fallbackValue”); // returns “fallbackValue”
Why use Optionals?
Optionals are a great alternative to using null or undefined for variables that don't yet have values for two main reasons:
They’re essential for functional programming because they clearly denote variables whose value is optional, forcing you to handle - or at least think about - both the case where they do and do not have a value.
Unlike with a variable that might be null or undefined, where you are able to operate on the variable without checking what it is (which, if you forget to check, might cause bugs), with Optionals you have to check if it has a value before operating on it.
At the end of the day, it decreases the chance you will have bugs caused by values not existing when they should - or vice versa.
Optionals improve readability because if you see that a variable is an Optional, especially if you are using types, you can know at a glance that it might not have a value (yet), and that you may have to handle both the case where it has a value and where it doesn’t. This can remove the need to track variables through code to figure out what values they might have at different points.
If you are strict about using Optionals, and not allowing null or undefined values, you can also know at a glance that any variable that isn’t an Optional must have a value at all times, which can further improve readability.
Optionals - and the helper functions you can use with them - can make your code less verbose, too, which is always nice.
That said, my team and I avoid null and undefined as much as possible. It does mean using a library that implements Optionals, and teaching new hires how to use them, but to us the benefits are worth it.
However, if you don’t want to jump all the way to Optionals, there are other alternatives out there…
Other alternatives to null and undefined
1. Optional chaining
Optional chaining (?.) allows you to read a property’s value within a chain of connected Objects, without needing to validate each reference in the chain.
2. Nullish coalescing operator
Nullish coalescing operator (??) is a new short-circuiting operator that returns its right-hand operand when its left-hand operand is null or undefined, otherwise returning its left-hand operand. It’s a good alternative to the logical OR operator (||) if you specifically want to check for null or undefined, since logical OR checks for any falsey value. It can also be a good shorthand syntax for selecting the first defined value from a list of values that might be null or undefined.
3. Default parameters
Default parameters enable you to say “if this is undefined, use this instead” when passing something into an argument. Note that this does not work with null though - null will be passed through as a valid value.
If you're dealing with async data, Promises may also be a helpful alternative as they have similar concepts of "has a value" and "doesn't have a value". They’re mainly used for asynchronous actions like calling to a database and waiting for the value to return though - in other words, things that might take time. And Promises can come with more overhead, which you may not want for simpler use cases.
If you find that the above alternatives aren’t enough, and that you’re either having to check for null or undefined a lot… you may save more time by using Optionals.
Try out Optionals using Tiny’s open source library!
If you’re sick of the null and undefined mess, maybe it’s time to try something different?
Changing the way you do things will take some work, but consider how much work you’re already put into managing the null/undefined mess with:
- The mental overhead of having to remember to check for null and undefined when writing and reviewing code
- The code bloat of having to implement checks for null and undefined everywhere they might cause problems
- Time lost tracking down and fixing bugs caused by variables being in an unexpected state
If you’re ready to try Optionals in your code, you can check out Tiny’s Katamari, which is one of our libraries of utility functions. But, be warned, we are in the middle of changing our Optionals implementation a lot, so you might prefer to try fp-ts as an alternative in the meantime.
Let’s continue the discussion
Want to share/rant about your experience with null or undefined? Or share how you’re using Katamari in your project? We’re always up for a chat on Twitter at @joinTiny!
And if you’re looking for more ways to improve the way you do programming, check out previous blogs in our programming principles series: