Operator precedence might be one of the most confusing parts of the Ruby language. Even the most popular style guides recommend you just use && and || over AND
and 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 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 OR function evaluates to true, the overall value must be true.
Think about the order in which they are evaluated (the precedence).
- &&
- =
- and
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 && operator?
(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 true
, and 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 true
.
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
What about || and OR?
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.