2

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 =?

Kai Sasaki
  • 667
  • 4
  • 13
  • 1
    You may find [this precedence table](https://docs.ruby-lang.org/en/2.6.0/syntax/precedence_rdoc.html) helpful. – Cary Swoveland Aug 03 '20 at 16:47
  • @KaiSasaki : Just FYI: Since your interesting question did not IMO get a satisfiable answer, I took the liberty to [ask it again](https://stackoverflow.com/questions/63390971/is-the-assignment-operator-really-just-an-operator), but boilded down to the essential problem in it. – user1934428 Aug 13 '20 at 08:21
  • Does this answer your question? [Is the assignment operator really "just" an operator?](https://stackoverflow.com/questions/63390971/is-the-assignment-operator-really-just-an-operator) – user1934428 Aug 14 '20 at 06:10

3 Answers3

4

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".

cesartalves
  • 1,507
  • 9
  • 18
  • _"everything returns a value"_ – expressions do. But Ruby also has statements like `break`, `next`, modifier-`if`, or `return` itself that don't have a return value. (trying to use them as a value results in a syntax error) – Stefan Aug 03 '20 at 13:18
  • 1
    `if` returns it's body if the condition is met, if not it returns `nil`. `return` returns `nil`. Yes, I suppose `break` and `next` indeed don't return, and `= return` will result in a syntax error. Aside from that, pretty much everything returns a value. Also, you can use `= if` in assignments, as long as it's closed with an `end`. I will edit my answer to remove that statement you pointed out. – cesartalves Aug 03 '20 at 13:30
  • In `true && true || false`, what does "&& returns the *last value* to the right" mean? The expressions "first value" and "last value" are vague, so should be avoided. In `m1 = s.match(p1) && true` you will agree that `s.match(p1)` is evaluated before `&&`. But why? What is the point of speculating in an answer (your last paragraph)? Should readers not expect answers to be definitive? – Cary Swoveland Aug 03 '20 at 16:37
  • My answer was too long for a comment. I did not intend to give a definite answer, but instead give my view on the subject. Isn't the point of a question to elicit possible answers, until one of them is chosen as correct? I do agree that "first value" and "last value are vague"; I might improve that definition and also the answer itself. – cesartalves Aug 03 '20 at 17:17
  • I've reordered the evaluation, for `s.match(p1)` indeed is evaluated before `&&`. – cesartalves Aug 03 '20 at 17:35
0

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
Sampat Badhe
  • 8,710
  • 7
  • 33
  • 49
  • 1
    I think this does not quite explain the question. Think of the expresssion `(a=1 && b=2)`, which is valid and assigns 2 to both a and b. If we look at the precedence rule, this expression should be interpreted as `(a=(1 && b)=2)`, since `&&` binds higher than `=`. However, the latter is plain wrong. – user1934428 Aug 03 '20 at 13:40
  • @user1934428 `(a=1 && b=2)` will interpreted as `(a=(1 && b=2))` which is why it assigns 2 to both a and b – Sampat Badhe Aug 03 '20 at 14:48
  • @user1934428 `&&` will have higher precedence, so it will check for left side and right side to perform the operation. `(a=1 && b=2)` in this case left side will be `1` and right side will be `b=2`. – Sampat Badhe Aug 03 '20 at 14:57
  • Aside from the fact that in this example, a will not be 1, but 2 (try it in irb!), I can't follow your logic. For instance, `*` has higher precedence than `+`, and `a+x * b+y`, the implicit binding is `a+(x*b)+y`. This is what higher precedence means. Following this logic, `a=1 && b=2` would have `a=(1 && b)=2`, if we observe only precedence. – user1934428 Aug 04 '20 at 06:18
  • @user1934428 yes you are right `a` will be `2` . My above comment seems little confusing. `(a=1 && b=2)` with this example. `&&` will have higher precedence, so it will check for left side and right side to perform the operation. So first operation will be `1 && b=2` which returns `2` and here left side will be `1` and right side will be `b=2` and in second operation the result of first operation will get assigned to `a`. it will be executed like `(a=(1 && b=2))` – Sampat Badhe Aug 04 '20 at 13:48
  • @SampathBadhe: The result you get is correct, but your explanation does not look right to me. If you look at `1 && b = 2`, and compare it to, say, `1 * b +2`, observing that the latter would mean to calculate it as `(1 * b) + 2`, we might conclude that the former should be interpreted likewise as `(1 && b) = 2`, which obviously is **not** the case. Why? – user1934428 Aug 05 '20 at 06:23
  • @user1934428 The example which you are referring will not work similar to the example mentioned in the question. `+` and `=` operator are not same. – Sampat Badhe Aug 05 '20 at 14:07
  • 1
    Yes, and therefore we can not explain it with operator precedence alone. I think this is also what caused the OP to ask this question. However I can't find anything in the Ruby docs which would explain this case. If you can, it would be great to include this in your answer. – user1934428 Aug 06 '20 at 06:28
  • @user1934428 I found one doc which is worth reading for understanding precedence, but didnt mentioned explicitly about the details which we are looking for. https://learn.microsoft.com/en-us/cpp/c-language/precedence-and-order-of-evaluation With Precedence, Associativity also matters while executing multiple operations. For `+` it performs `Left to right` and for `=` it performs `Right to left`, I'll find more details and update my answer accordingly. – Sampat Badhe Aug 06 '20 at 15:00
  • The page you are refering to is about C, not Ruby, so it is not relevant here. I also thought that this can be explained with associativity, but associativity only affects if we have several operators of the same precedence in a row. For example, `-` is left-associative, which you need when calculating `a-b-c`, which must be interpreted as `(a-b)-c`. If there is a higher-precedence operator in between, i.e. `a-b*c-d`, that operator binds first, `a-(b*c)-d`, and **then** we apply associativity, `(a-(b*c))-d`. – user1934428 Aug 07 '20 at 05:50
  • @user1934428 here is article for ruby - https://www.oreilly.com/library/view/the-ruby-programming/9780596516178/ch04s06.html#tab-operators – Sampat Badhe Aug 10 '20 at 11:42
  • While the article itself is well written and helpful in general, I don't quite see which part of it would deal with the problem we discuss here. For the assignment operator, it merely states that it is right-associative, which is something we already know. – user1934428 Aug 10 '20 at 12:02
0

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)
Ben Trewern
  • 1,583
  • 1
  • 10
  • 13
  • 1
    As you said, I thought the code would throw an error. But it returns an unexpected value instead. Writing `if m1 = (s.match(p1) && m2) = s.match(p2)` explicitly raised an error. But `if m1 = s.match(p1) && m2) = s.match(p2)` did not. Why cannot I get an error? – Kai Sasaki Aug 03 '20 at 21:35
  • I'm guessing (can't find any documentation for this) that the ruby parser finds a syntax error and does the assignment first to fix the error. I did find another discussion on stackoverflow about this but I can't find it now. – Ben Trewern Aug 04 '20 at 14:39
  • @BenTrewern : Your argument about right-associativity does not convince me. Associativity disambiguates if we have several operators of the same precedence. For instance, in `a-b-c-d`, left associativity guarantees us `((a-b)-c)-d`. This does not matter if one of the operators has higher precedence. For instance `a-b*c-d` does not calculate `a-b` first, even though `-` is left-associative, simply because `*` has higher precedence. – user1934428 Aug 10 '20 at 09:00
  • 1
    Convince you of what? My point was there is something involved that is not precedence or associativity. My guess is that the parser ends up doing `(m2 = s.match(p2))` first otherwise there would be a syntax error. It would be nice to find some documentation on exactly why this happens but I haven't seen any. – Ben Trewern Aug 11 '20 at 00:17
  • @BenTrewern : In this case I agree with you (misunderstood you). I also think that it can't be explained by precedence and associativity alone, but I can't find an explanation for it in the Ruby Docs either. I find the question interesting and will ask a new version of it (more focusing on the real issue here), in the hope to attrackt the attentions of language lawyers. Actually, even Matz himself seems to visit SO from time to time .... – user1934428 Aug 13 '20 at 07:59