The Untapped Potential of ‘Nested‘ Ternaries
We’ve been using these things incorrectly all along!
Very soon, I will reveal to you the untapped potential of “nested” ternaries in JavaScript. (The use of quotes around nested will also be explained). But first, let’s talk about pattern matching!
Pattern matching is found in many programming languages, including Scala and Haskell. It is used to identify patterns, and it offers us a clear, concise way to determine how our code will branch.
Here’s an example of pattern matching in Scala from Bruce Tate’s excellent Seven Languages in Seven Weeks:
def doChore(chore: String): String = chore match {
case “clean dishes” => “scrub, dry”
case “cook dinner” => “chop, sizzle”
case _ => “whine, complain”
}doChore(“clean dishes”) // -> "scrub, dry"
doChore(“mow lawn”) // -> "whine, complain"
Following the logic is simple. We just scan the list for the first match, and there we find the value to be returned. If we reach the end of the list and haven’t come across the case we’re looking for, there at the end we’ll find our return value, a catch-all for any case we didn’t account for.
Here’s a look at pattern matching in Haskell to calculate factorials:
factorial :: Integer -> Integer
factorial 0 = 1
factorial x = x * factorial (x - 1)-- factorial 0 -> 1
-- factorial 1 -> 1
-- factorial 17 -> 355687428096000
And just for fun, a Fibonacci sequence in Prolog:
fib(0, 1).
fib(B, C) :- fib(A, B), C is A + B.% fib(0, X) -> X = 1.
% fib(1, X) -> X = 1, X = 2.
% fib(144, X) -> X = 233.
We can’t use pattern matching as a control structure in JavaScript, but if you squint, case "clean dishes"
(from our Scala example) starts to look a bit like a Boolean test, and those we do have. Keep squinting, and this JavaScript code looks a bit like pattern matching:
let result;if (operator === ‘+’) {
result = left + right;
} else if (operator === ‘*’) {
result = left * right;
} else if (operator === ‘-’) {
result = left - right;
} else {
result = left / right;
}
But we can do better than this! Some problems with this code:
- We’re using
let
even though we knowconst
is preferred. - We’re changing the value of a variable that was declared outside of our block scope.
- We’re repeating ourselves by typing our assignment
result =
multiple times.
Let’s see what we can do with a nested ternary instead, shall we?
const result =
operator === ‘+’ ? left + right
: operator === ‘*’ ? left * right
: operator === ‘-’ ? left — right
: left / right;
Hey, that’s not too bad! Let’s take a look back at what we said about pattern matching:
Following the logic is simple. We just scan the list for the first match, and there we find the value to be returned. If we reach the end of the list and haven’t come across the case we’re looking for, there at the end we’ll find our return value, a catch-all for any case we didn’t account for.
Looks like we’ve got ourselves a tool that functions a lot like pattern matching already available to us in JavaScript!
It’s important to notice why we were able to do this — we formatted our “nested” ternary such that each line has a single condition and a single result, with the catch-all at the end. Because of this, we can read and write it as though it isn’t nested at all. Hence the quotes. 😉 There’s no need to worry about remembering each nested context. Once you’ve ruled out that a line would apply to the case you’re considering, you can safely forget it.
It’s generally considered best-practice to avoid nested ternaries in JavaScript, but that’s because we usually write code that looks like this:
var thing = foo ? bar : baz === qux ? quxx : foobar;
If you think that looks confusing, that’s because it is! ESLint suggests that the solution is to write code like this:
var thing;if (foo) {
thing = bar;
} else if (baz === qux) {
thing = quxx;
} else {
thing = foobar;
}
But hopefully now you realize there’s a better alternative:
var thing =
foo ? bar
: baz === qux ? quxx
: foobar;
You can follow me on Twitter @okaybenji