Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it okay to write or but not ||?

Tags:

operators

ruby

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?

like image 990
mbigras Avatar asked Jan 05 '23 21:01

mbigras


2 Answers

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]
like image 200
Eric Duminil Avatar answered Jan 14 '23 13:01

Eric Duminil


|| 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
like image 43
akuhn Avatar answered Jan 14 '23 14:01

akuhn