10

Previous answers

The answer to a similar question is wrong.

Method calls are mentionned neither in Ruby documentation nor in the community wiki.

Method call without parentheses

Higher than or

or seems to have a lower precedence than a method call without parentheses :

puts false or true

is equivalent to

( puts false ) or true

and displays false.

NOTE: I know or shouldn't be used. Still, it's a good example to show that some operators do have lower precedence than method calls.

Lower than ||

puts false || true

is equivalent to

puts (false || true)

and displays true.

Method call with parentheses

The parentheses used for method call don't seem to be grouping :

puts(false or true)
# SyntaxError: unexpected keyword_or
puts((false or true))
#=> true

Question

Where should method calls with and without parentheses be in this precedence table?

Bounty clarification

I'm looking for the exact location of method calls in the table. Preferably with examples proving it's lower than the previous one and higher than the next one.

The current answers also don't seem to mention method calls with parentheses.

Thanks in advance!

Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • 1
    `puts(false or true) # => SyntaxError` wtf? – ndnenkov Dec 24 '16 at 14:47
  • 1
    The "wordy" logical operators are a bit pathological. You'd need to do `puts((false or true))` to give a valid expression to the `puts` method. – Dave Newton Dec 24 '16 at 14:51
  • 1
    @ndn wtf indeed! :) I was really surprised by that one. `puts(false or true)` is parsed as `{puts(false} or true)` – Eric Duminil Dec 24 '16 at 14:52
  • Why did you open a bounty on this? Didn't akuhn answer the question? If it's the `SyntaxError` thingy, it would have been more appropriate to open a new question. – ndnenkov Feb 06 '17 at 13:54
  • 1
    Congratz on the Ruby gold btw ^^ – ndnenkov Feb 06 '17 at 13:54
  • Thanks. I updated the question. I should have written a better description in the bounty, sorry about that. – Eric Duminil Feb 06 '17 at 14:29
  • I see you're not far away of Ruby gold yourself. keep up the good work (preferrably with another avatar ;) ) – Eric Duminil Feb 06 '17 at 14:30
  • @EricDuminil How did you end up with `{puts(false} or true)`? Try `ruby -y -e "puts(false or true)"` – bliof Feb 06 '17 at 22:25
  • @bliof: It's just a way to show how Ruby tries to parse `puts(false or true)`, and why it fails. The equivalent isn't correct Ruby either. Does this [thread](http://stackoverflow.com/questions/37613287/why-does-putsnil-or-4-fail-in-ruby/37615125#37615125) answer your question? – Eric Duminil Feb 06 '17 at 22:29
  • ¯\_(ツ)_/¯ https://github.com/ruby/ruby/blob/v2_4_0/parse.y#L1413 tl&dr; `keyword_or` needs a valid expresion on the left and on the right and on the left it is `puts(false` which does not have a closing bracket. – bliof Feb 06 '17 at 22:32
  • 1
    @bliof I think we agree on this one.`{puts(false}` was a (weird) way to write what you just commented. – Eric Duminil Feb 06 '17 at 22:34

3 Answers3

8

Prelude

This aims to test all possible scenarios.

Note that when saying "operator X has higher precedence than method invocation" what is meant is in arguments. Aka:

invocation foo X bar

as opposed to (call on object)

X invocation

As far as the second case is concerned, method calls always have higher precedence.


Short answer

It doesn't fit:

  • It causes SyntaxError in some cases
  • It has higher precedence than rescue, but lower than assignment

Summary

  • not can't be used after method invocation regardless of brackets
  • Using brackets (()) with method invocations sometimes causes a SyntaxError. These cases are: and, or, if, unless, until, while and rescue
  • In cases when brackets don't cause an error, they don't change the precedence in any way
  • All operators, except for and, or, postfix if, unless, until, while, rescue have higher precedence than method invocation

Lets try it:

class Noone < BasicObject
  undef_method :!

  def initialize(order)
    @order = order
  end

  def method_missing(name, *args)
    @order << name
    self
  end
end

First unary:

# + and - will become binary
unary_operators = %i(! ~ not defined?)

puts 'No brackets'
unary_operators.each do |operator|
  puts operator

  order = []
  foo = Noone.new order
  bar = Noone.new order
  begin
    eval("foo.meta #{operator} bar")
  rescue SyntaxError => e
    puts e
  end
  p order
  puts '-----------'
end

puts 'Brackets'
unary_operators.each do |operator|
  puts operator

  order = []
  foo = Noone.new order
  bar = Noone.new order
  begin
    eval("foo.meta(#{operator} bar)")
  rescue SyntaxError => e
    puts e
  end
  p order
  puts '-----------'
end

Points taken:

  • not after a method invocation is a SyntaxError
  • all unary operators have higher precedence than method invocation regardless of brackets

Now binary:

binary_operators = %i(
  **
  * / %
  + -
  << >>
  &
  | ^
  > >= < <=
  <=> == === =~
  .. ...
  or and
)

puts 'No brackets'
binary_operators.each do |operator|
  order = []
  foo = Noone.new order
  bar = Noone.new order
  baz = Noone.new order
  begin
    eval("foo.meta bar #{operator} baz")
  rescue SyntaxError => e
    puts e
  end
  p order
end

puts 'Brackets'
binary_operators.each do |operator|
  order = []
  foo = Noone.new order
  bar = Noone.new order
  baz = Noone.new order
  begin
    eval("foo.meta( bar #{operator} baz)")
  rescue SyntaxError => e
    puts e
  end
  p order
end

Points taken:

  • brackets around method invocation with and or or is a SyntaxError
  • we have to test and and or further without brackets
  • .. and ... call <=>. We have to test this further
  • we couldn't test a few other binary operators this way, namely &&, ||, ==, !=, modifier rescue, if, unless, until, while
  • other than the above mentioned, operators have higher precedence, regardless of brackets

def yes
  puts 'yes'
  true
end

def no
  puts 'no'
  false
end

def anything(arg)
  puts 'Anything'
  arg
end

anything yes and no
anything no or yes
anything yes && no
anything no || yes
anything(yes && no)
anything(no || yes)

anything yes == no
anything(yes == no)
anything yes != no
anything(yes != no)

Points taken:

  • and and or have lower precedence without brackets
  • &&, ||, == and != have higher precedence regardless of brackets

def five(*args)
  p args
  5
end

five 2..7
five(2..7)
five 2...7
five(2...7)

Points taken:

  • .. and ... have higher precedence regardless of brackets

anything yes if no
anything(yes if no)
anything no unless yes
anything(no unless yes)

anything no until yes
anything(no until yes)
anything yes while no
anything(yes while no)

Points taken:

  • brackets with if, unless, until, while cause a SyntaxError
  • all of the above have lower precedence than method invocation without brackets

def error
  puts 'Error'
  raise
end

anything error rescue yes
anything(error rescue yes)

Points taken:

  • brackets around rescue cause a SyntaxError
  • rescue has lower precedence if no brackets are present

Ternary:

anything yes ? no : 42
anything(yes ? no : 42)

Points taken:

  • ternary has higher precedence regardless of brackets

Assignment (left for last as it changes yes and no):

anything yes = no
anything(no = five(42))

Points taken:

  • Assignment has higher precedence than invocation

Note that += and the like are just shortcuts for + and = so they exhibit the same behaviour.

ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • Very impressive, thanks. I really like the innovative way to check the precedence with `eval` and `order`. I never thought that it would make sense (or be possible at all) to rescue a `SyntaxError`. Note that `not` can be used as argument : `foo.meta((not bar))` – Eric Duminil Feb 06 '17 at 22:17
  • 1
    @EricDuminil, well not in that sense. But I agree that I did speak in absolutes which can be misinterpreted. Brackets in this answer refer to the brackets of method invocations and do not include ones used for grouping. – ndnenkov Feb 06 '17 at 22:26
  • Does it mean that precedence cannot be defined as as [partial order](https://en.wikipedia.org/wiki/Partially_ordered_set) for the `operators + method call` set because it isn't transitive and isn't even defined for every pair? In that case, it would explain why [total order](https://en.wikipedia.org/wiki/Total_order) cannot be achieved and why the table doesn't include method calls. – Eric Duminil Feb 08 '17 at 10:40
  • 1
    @EricDuminil, generally speaking, you can define order over incomparable set - any permutation would do (even if it will be absolutely impractical for language documentation to do). But it is indeed the case that if you add method calls (as understood in this context), the comparison is no longer transitive. Hence there is no total order. – ndnenkov Feb 08 '17 at 10:53
  • Great job. Thanks. – Eric Duminil Feb 08 '17 at 10:57
2

In Ruby, method call precedence seems to be lower than defined? but higher than or.

For example:

puts defined? true
#=> true

puts false or true
#=> prints `false` and returns `true`

Note: puts(not true) and puts(false or true) raise syntax errors.

Fede
  • 460
  • 3
  • 10
  • 1
    `or` is lower than `defined?`. Do you mean lower than `defined?` and higher than `or`?. It seems to be true. method calls without parens seem to be between `defined?` and `not`. – Eric Duminil Dec 24 '16 at 15:00
  • 1
    You are right, I mixed them when writing the answer. I already edited it. – Fede Dec 24 '16 at 20:45
2

Updating to actually answer the question.

Officially methods don't have a precedence. However as you demonstrate we can sort them into the precedence list and they fall between what we could consider "operators" and what we could consider "control flow" keywords.

See, https://ruby-doc.org/core-2.2.0/doc/syntax/precedence_rdoc.html

Which starts with operators, and ends with control-flow constructs like

?, :
modifier-rescue
=, +=, -=, etc.
defined?
not
or, and
modifier-if, modifier-unless, modifier-while, modifier-until

The only oddball there is defined? of which I don't understand why it hasn't been defined as a global function on the Kernel module anyway.

Missing raise, loop, catch/throw and others?

They are not keywords but method calls that are defined as module_function on the Kernel module. And since this module is included in Object they are made into private methods of all classes and thus appear to be global functions that are available everywhere.

Hope that helps to answer the question. Sorry for the original copypasta.

akuhn
  • 27,477
  • 2
  • 76
  • 91
  • Uh oh, you're right. Copypasta mistake. Let me update this one here … – akuhn Dec 25 '16 at 10:16
  • Thanks a lot. "Officially methods don't have a precedence". Do you know why? Can they be added into the table you link to, or do the precedence depends on other factors? – Eric Duminil Dec 25 '16 at 10:29
  • I don't know, maybe just an omission? – akuhn Dec 25 '16 at 10:32
  • @akuhn, `defined?` isn't a method as it works with code rather than string or something else. If it was a method and you used something like `defined? x`, this will actually evaluate `x` before passing it to the method. You might work around this if you use a string `defined? 'x'`, but then are you referring to the local variable `x` or the string `'x'`? You can add special notation for one or the other and it gets complex. – ndnenkov Feb 06 '17 at 10:12
  • This answer goes in the right direction. It doesn't pinpoint the exact location of where method call would be in the precedence table, though. Both with and without parens, pretty please :) – Eric Duminil Feb 06 '17 at 14:23