The Logic Behind Logical Operators
One of the most compelling aspects of the Ruby language is the sweetness of its syntactic sugar. This made the almost ubiquitous use of the
|| operators over their semantically pleasing counterparts and and or all the more surprising to me. It turns out the two sets of operators are actually different in a meaningful way.
So what’s the difference?
Precedence. Huh? Precedence is the same as Order of Operations. Remember PEMDAS from grade school? It’s that acronym for Parentheses, Exponents, Multiplication, Division, Addition, Subtraction. The operators at the beginning of PEMDAS (parentheses) are considered first, then the next (exponents), and so on. Operator precedence in Ruby and other programming languages works the same way — there are just more operators. Got it? Got it. Moving on…
|| have a higher precedence than and and or. This means Ruby will consider them first. Seems kind of harmless as long as we’re not mixing the two sets of operators (e.g. using
or) in the same expression, but the real problem doesn’t lie at either of these levels of precedence. The troublesome area is the space between.
Assignment operators fall below
||, but above and and or. This starts to cause all sorts of problems when we use the latter because expressions stop behaving the way we assume they should.
1 2 3 4 5 6 7 8 9
Let’s say we want to compare two values and assign their return value to a variable. The first set of operations occurs how we expect. The expression
false || true returns
true is assigned to
value. The second set of operations looks like it should do the same, but it doesn’t. The
= assignment operator has the highest precedence, so
false is assigned to
value is compared to
true via the
or operator. This returns
value returns false, which wasn’t our intention.
1 2 3 4 5 6 7 8 9
We encounter the same problem with the code above.
There’s another key difference between these two sets of operators.
&& is actually one level of precedence higher than
||, whereas and and or share their level of precedence. I assume this is the case because of the assumption that we are more likely to compare chains of
&& expressions with the
|| operator than vice versa because the former has more practical implementations.
or have identical precedence, so their expressions are executed in the order in which they occur, which is rarely our intention.
Cool, I get how it works. Now I can use and and or, right?
Wrong. Why would you do that to yourself? Because it’s prettier? Unless you’re writing specific types of expressions, you’ll have to resort to using unnecessary parentheses. Gross.
Because you just read this awesome blog post and now you know how they work? Well, did everyone you know read this blog post? I didn’t think so. Even if you know your code does one thing, your collaborators will probably assume it does something else.
||have a higher precedence than
- Assignment operators, like =, have a lower precedence than
||(good), but higher than
&&has higher precedence than
orhave the same (bad).
||is the convention for a reason. Don’t be that guy.
- If you get confused, just use parentheses. Parentheses are your friends. Make it work now. Refactor later.