Understanding Boolean Operator Precedence in Ruby (&&, and, ||, or)
Operator precedence might be one of the most confusing parts of the Ruby language. Even the most popular style guides recommend you just use
OR. But just for kicks, let’s figure out once and forever how this thing actually works.
As always, an example is worth a thousand pictures.
result = true and false result # => true result = true && false result # => false
So why is there a difference between those two. I know when I first saw the two variants (
AND) I thought,
AND is just a nicer way of writing
&&. But it’s not.
Ruby uses Short-circuit evaluation, and so it evaluates the first argument to decide if it should continue with the second one.
When the first argument of the AND function evaluates to
false, the overall value must be
false; and when the first argument of the
ORfunction evaluates to
true, the overall value must be true.
Think about the order in which they are evaluated (the precedence).
So let’s apply that to the example.
((result = true) and false) # => true
So applying the short-circuit evaluation here, looks like this.
When the first argument of the AND function (i.e. the
result = true, because
= has higher precedence than
and) evaluates to
false, the overall value must be false. Well, in this case the value is not
false, so the value of the first expression is returned instead.
How about the
(result = (true && false)) # => false
The fact that
&& has higher precedence than the assignment operator (
=), makes it so that the arguments to the AND function are
false. The ones in the inner parenthesis. And because of the short-circuit evaluation, the first value is returned. Thus the value of the expression is
Think control flow
The main purpose of the
and operator is actually to control the flow of logic.
You can use the
and operator for chaining operations that depend on each other.
buy_milk() and cook_breakfast() and feed_family()
You can also think of
and as a reversed
if statement modifier.
next if result = 2.even? # becomes result = 2.even? and next
It’s the same thing. The right hand side is never evaluated if the left hand side is truthy. Let’s prove this.
true || baz() # => true false || baz() # => NoMethodError: undefined method `baz' true or baz() # => true false or baz() # => NoMethodError: undefined method `baz'
So you could do something cool like this.
cook_dinner() or raise(RuntimeError, "Not in the mood.")
That works because the right hand side will only be evaluated if
cook_dinner() evaluates to false (i.e. not truthy).
Think control flow
or is like a reversed unless modifier. So the previous example could be written like this.
raise(RuntimeError, "Not in the mood.") unless cook_dinner()
Choosing the right one of these options could improve the readability of your code.