4

I understand there is a difference in the precedence as shown in another answer:

p foo = false || true
# => true

p foo = false or true
# => false

But it seems like there is something more that's different between or and ||.

For example:

p foo = 42 or raise "Something went wrong with foo"
# => 42
p foo = nil or raise "Something went wrong with foo"
# => Something went wrong with foo (RuntimeError)
p foo = 42 || raise "Something went wrong with foo"
# => syntax error, unexpected tOP_ASGN, expecting end-of-input

I was expecting to get:

p foo = 42 or raise "Something went wrong with foo"
# => 42
p foo = nil or raise "Something went wrong with foo"
# => Something went wrong with foo (RuntimeError)
p foo = 42 || raise "Something went wrong with foo"
# => Something went wrong with foo (RuntimeError)

But it's a syntax error. So what is happening?

Community
  • 1
  • 1
mbigras
  • 7,664
  • 11
  • 50
  • 111
  • 1
    Many Rubiests, myself included, *never* use `and` or `or`. Sticking with `&&` and `||` will simplify your life without limiting your options. – Cary Swoveland Dec 24 '16 at 02:35

2 Answers2

8

Theory :

Here's a precedence table for Ruby.

It's not clear from this table but Ruby method invocation without parentheses has a lower precedence than || and =, but higher than or. See this question.

So for your code, from highest to lowest precedence :

  • ||
  • =
  • raise "something"
  • or

Expression with or

foo = 42 or raise "Something went wrong with foo"

First comes = :

( foo = 42 ) or raise "Something went wrong with foo"

Then raise :

( foo = 42 ) or ( raise "Something went wrong with foo" )

Then or :

( ( foo = 42 ) or ( raise "Something went wrong with foo" ) )

Expression with ||

foo = 42 || raise "Something went wrong with foo"

First comes || :

foo = ( 42 || raise ) "Something went wrong with foo"

Here's your syntax error!

You want :

foo = 42 || (raise "Something went wrong with foo") #=> 42

or

foo = 42 || raise("Something went wrong with foo")  #=> 42

or just

foo = 42 || raise 

Warning!

When you have troubles with precedence, you should be careful about adding another puts or p without parentheses !

For example :

p [1,2,3].map do |i|
  i*2
end

outputs :

#<Enumerator: [1, 2, 3]:map>

even though you might have expected :

[2, 4, 6]
Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • Updated. Sorry, my first answer was wrong. `raise "something"` has higher precedence than `or`, even though I cannot find any official statement about it. – Eric Duminil Dec 24 '16 at 00:42
  • That's because `raise` is a method call. It is one of the global functions defined on the `Kernel` module and hence has the same syntax and precedence as any method call. – akuhn Dec 25 '16 at 10:13
1

|| and or are not the same operation.

The first is equivalent to a method call, the latter is a control flow keyword. You probably always want to use || to avoid confusion with precedence. Most style guides for Ruby have a clause that bans the use of and and or for that reason.

So then,

A or B

# can be considered equivalent to

if A then A else B end

whereas

A || B

# can be considered equivalent to

A.or { B } # given a hypothetical "logical or" method

Now let's look into your or example

p foo = false or true

is equivalent to

temp = p(foo = false) # => nil
if temp
  temp
else
  true
end

and thus when executed prints false and returns true

[1] pry(main)> p foo = false or true
false
=> true
[2] pry(main)> foo
=> false

whereas

p foo = false || true

is equivalent to (glossing over the difference between boolean and logical OR for now since your example is dealing with booleans anyway)

p(foo = false.|(true))

and thus when executed prints true and returns true

[1] pry(main)> p foo = false || true
true
=> true
[2] pry(main)> foo
=> true
akuhn
  • 27,477
  • 2
  • 76
  • 91
  • Same answer for 2 different questions, and you manage to miss the point on both! You don't explain the SyntaxError. `||` isn't equivalent to a method call because they don't have the same precedence at all, and `A || B` can also be seen as `if A then A else B end`. – Eric Duminil Dec 25 '16 at 09:53
  • You're right, they actually produce the same bytecode. Conceptually though one is meant to be a method-like operator while the other is meant for control flow. I can explain the syntax error … oh, and you do in your answer! – akuhn Dec 25 '16 at 10:09