Ruby's or operator precedence


Operators have precedence and sometimes they can give us hard time to figure out why our code works as it does.

Ruby has three or operators, |, ||, and the readable or. All of them have different precedences. That is, the precedence, that makes some trouble if we do not care about which one we use.

The evaluation of logical operators are left-associative, that means the far left operator is evaluated at first, than its result will be the left operand of the next operator and so on. | operator evaluates both operands, while || or or are short-circuit operators.

There are two falsy values in the language, false and nil, anything else belong to the truthy set. | operator works as expected and returns logical values.

puts false | nil  # false
puts true | nil   # true
puts 2 == 2 | 2   # true
puts 2 == 2 | 3   # false, here 3 is consider as an int value, not a logical value  

In case of || and or operators returned value is not logical, as it would be in case of |. Nothing has suprised us!

puts 2 == 2 || 3  # 3
puts 3 || 2 == 2  # true
puts 2 == 2 or 3  # 3
puts 3 or 2 == 2  # true

Let us see a bit more complex example and examine the results. Since both || and or are lazy operators, if left-hand side operand is true, the right-hand side operand won’t be evaluated, hence we understand why empty string is emitted instead of 3 in place of b.

a = 2 || b = 3
puts "|| '#{ a }' '#{ b }'"  # || '2' ''
a = 2 or b = 3
puts "or '#{ a }' '#{ b }'"  # or '2' ''

What if the right-hand side operator is also evaluated, since the result of the expression is false on the left.

a = false || b = 3
puts "|| '#{ a }' '#{ b }'"  # || '3' '3'
a = false or b = 3  
puts "or '#{ a }' '#{ b }'"  # or 'false' '3'

Uhh, quite wierd results. Not just they are different, but the value of a is the same as the value of b. Where is false?

The trick is that the precedence of || is higher than the precedence of = (assign), but the precedence of = is higher than the precedence of or.

According to the abovementioned lines, the evaluation of || looks as follows:

a = (false || b = 3) 
a = ( b = 3 ) 
a = 3

And operators, like &, &&, and and, work similar.

As we see the precedence of operators can cause a big mess if we are not circumspect enough. Though, if it is possible, use || operator and be careful.

Sample can be found: https://gist.github.com/torokmark/8347c58f0cda918e99cb43fba84aea81