January 09, 2020
4 min read ☕️
Click now Keep reading to see the beginner's guide to what computedValue && "One Truthy Boi"
evaluates to!
To truthy, or not to truthy?
Let's do a little code experiment.
How does the value of computedValue
affect what the expression computedValue && "One Truthy Boi"
evaluates to?
Well, let's take a gander in ${insertYourFavoriteJavascriptConsole}
(I'll run mine in the Chrome browser console).
When computedValue
is truthy:
let computedValue = true;
let result = computedValue && 'One Truthy Boi';
console.log(result); // "One Truthy Boi"
Versus when computedValue
is falsey:
let computedValue = false;
let result = computedValue && 'One Truthy Boi';
console.log(result); // false
And to prove it, a screenshot of it running:
Strange.
So, result
will either be false
or "One Truthy Boi"
depending on what the left-hand-side of the &&
boolean operation evaluates to.
Interesting... 🤔
Why this is weird to me
It turns out this is a pretty standard feature in dynamic languages like JavaScript, Ruby, and Python. However, if you're coming from the land of types, this is... odd. To understand why let's take a look at a little history...
Normally, when performing (left expression && right expression)
, you're checking for a true
or false
evaluation, like in a conditional expression for an if/else
block.
This is called a boolean expression and evaluates to either true
OR false
(two values only; one or the other, not both).
In the ancient manuscripts C
language, boolean primitives weren't a thing until C99
.
By that I mean you couldn't assign a type of bool_t
to a variable before C99
.
Before the boolean types were added, other things acted as true
and false
.
Instead, boolean operations were done on integers, where 0
is false
and any other number is true
.
Similarly, in dynamically-typed languages, many things can act as true
and false
, even though they aren't literally true
or false
primitive values.
The expression true && "not a boolean"
works in dynamically-typed languages because true
is, well, true
and "not a boolean"
is truthy.
And by truthy, I mean that it ain't an empty string ""
!
A few more examples of...
Truthy things:
- non-empty arrays (in JavaScript)
- non-empty strings (in JavaScript)
- anything non-
nil
in Ruby, including 0! 😳
Falsey things:
- Empty strings (in JavaScript)
- Empty arrays (in JavaScript)
nil
,null
,undefined
But here's the kicker: in C
, boolean expressions are just that: boolean, one of two values.
In other words:
if (boolean_expression) {
// run this code if boolean_expression == TRUE
} else {
// run this code if boolean_expression == FALSE
}
But, the astute reader will notice that, for the typical dynamically-typed language, our boolean expressions evaluate to... anything! (Kind of...)
What's going on here?
Let's look at a quick example:
const leftHandSide = 'some value here';
const rightHandSide = 'One Truthy Boi';
const result = leftHandSide && rightHandSide;
// result = "One Truthy Boi"
In this case, the dynamically-typed runtime is coercing the left-hand side of the boolean expression into a boolean value, like:
const leftHandSide = 'some value here';
const rightHandSide = 'One Truthy Boi';
if (Boolean(leftHandSide) === true) {
return rightHandSide;
} else {
return false;
}
This is mostly true, but we'll clarify how it actually works next.
The main thing to take away is that if the leftHandSide
expression evaluates to false
, then a falsey value is returned.
Let's take a deeper look.
So far, we've only looked at &&
(AND), but there's also ||
(OR) and !
(NOT).
According to MDN on logical operators, each of these behaves like the following:
- Logical AND:
expr1 && expr2
- If
expr1
can be converted totrue
, returnsexpr2
; else, returnsexpr1
- If
- Logical OR:
expr1 || expr2
- If
expr1
can be converted totrue
, returnsexpr1
; else, returnsexpr2
- If
- Logical NOT:
!expr
- Returns
false
if it can be converted totrue
; else, returnstrue
- Returns
Take my word for it, but Ruby boolean expression behave pretty much identically.
So, did you catch it?
There's a big difference in what I, coming from a strongly typed background, would expect versus what the actual boolean operation defines:
In the case where the expression short-circuit evaluates, instead of returning a boolean literal (i.e.
true
orfalse
), it returns the value that forced the short-circuit path, e.g.undefined && true
returnsundefined
instead offalse
This is why in the original example we saw computedValue && "One Truthy Boi"
return either false
or "One Truthy Boi"
.
This is also why, in React, you can short circuit return jsx like:
function Example() {
const [showGreeting, setShowGreeting] = React.useState(false);
return (
<div>
<button type="button" onClick={() => setShowGreeting(!showGreeting)}>
{showGreeting ? 'Hide greeting' : 'Show greeting'}
</button>
{showGreeting && <p>Hello there! 👋</p>}
</div>
);
}
Conclusion
There's an immense amount of flexibility gained from operating as dynamically-typed languages do. Often though, it can produce surprising results if you're not careful. Usually, it works out well, but there are cases where short-circuit evaluation of boolean expressions (logical operators) can produce unexpected results.
I'll leave you with an example.
Take for instance the following React code.
What do you think happens when list.length === 0
?
Have a look for yourself below 👀:
function Example() {
const [list, setList] = React.useState([]);
function addItem() {
setList(list.concat(list.length + 1));
}
function clearList() {
setList([]);
}
return (
<div>
<p>List length: {list.length}</p>
<button type="button" onClick={addItem}>
Add One
</button>
<button type="button" onClick={clearList}>
Clear List
</button>
{list.length && (
<ul>
{list.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
)}
</div>
);
}
List length: 0
0