Does assignment precede logical operator in Ruby?

Viewed 301

I have encountered a strange situation in the if condition of Ruby. Here is the sample code to reproduce the situation.

p1 = /hello/
p2 = /world/

s = "hello, world"

if m1 = s.match(p1) && m2 = s.match(p2)
    puts "m1=#{m1}"
    puts "m2=#{m2}"
end

The output will be

m1=world
m2=world

But I expected m1=hello because the && operator has higher precedence in Ruby operators.

This code

if m1 = s.match(p1) && m2 = s.match(p2)

seems to be interpreted as

if m1 = (s.match(p1) && m2 = s.match(p2))

Why is the logical AND operator && preceded over the assignment operator =?

3 Answers

In ruby, pretty much everything (see comments) returns a value.

The operator && returns the last expression to its the right. So 1 && 3 yields 3. && will short circuit on the first falsey value. It returns either that value, or the last evaluated truthy expression.

|| returns the first expression to the its left - so 1 || 3 yields 1. || will short circuit on the first truthy value, returning it.

Check this difference:

1 + 5 * 3 + 1
# => 17 

1 + 5 && 3 + 1
# => 4

1 + 5 || 3 + 1
# => 6 

This is the order of evaluation in m1 = s.match(p1) && m2 = s.match(p2)

  1. s.match(p1) => "hello"
  2. && => evaluate everything to its right
  3. m2 = s.match(p2) => "world"
  4. m1 = "hello" && "world" => "world"

Your assignment to m2 returns the value which is used for the second part of the && expression, "world". Assignments in ruby also return a value!

So you will have m1 and m2 both with the value "world".

m1 = s.match(p1) && m2 = s.match(p2)

&& has higher precedence than = so it will perform && operation first which is below

s.match(p1) && m2 = s.match(p2)
=> 'hello' && m2 = 'world'
=> 'world'

it assigns world to m2 returns m2 which is world and then perform the next assignment operation.

m1 = {OUTPUT OF (s.match(p1) && m2 = s.match(p2))}

m1 = 'world'

Read about Boolean Operator Precedence in Ruby

Better you use brackets to make it more explicit

if (m1 = s.match(p1)) && (m2 = s.match(p2))
  ...
end

OR can perform the assignment operation first

m1 = s.match(p1)
m2 = s.match(p2)
if m1 && m2
  ...
end

Using just the precedence rules your code would read:

if m1 = (s.match(p1) && m2) = s.match(p2)

As = has right-to-left associativity you then get a syntax error when trying to do:

(s.match(p1) && m2) = s.match(p2)
Related