1

I am curious about how method calls relate to operator precedence. In irb, I see this:

var = puts(5)
5
=> nil
var
=> nil

This implies that the call to puts has higher precedence than the assignment operator, since nil (the return value of puts(5)) is assigned to var, rather than the method call itself. Because nil is assigned to var (as we can see on line 4), I would guess that puts(5) was called before the assignment operator.

In this Stackoverflow thread, everybody agrees that method-calls have lower precedence than every operator.

However this website lists the . as an operator for method-calls, and says that it is the highest-precedence operator.

If this second website is indeed accurate, I'm unsure about whether there is an implicit . operator when you call a method on main (and therefore about whether . being a high-precedence operator is sufficient to explain the irb session above).

In general, I'm curious about the order in which Ruby does things when it encounters a line of code, so if you know of any resources that explain that in an accessible way I would be interested in reading them.

EDIT: thanks for answers so far. Maybe I wasn't clear enough about my basic questions, which are theoretical not practical (so are arguably 'overthinking', depending on how much you like to think):

  • is . technically an operator, or technically not an operator?
  • is there a . somewhere behind the scenes every time you call a method?
  • are operators the basic way that Ruby decides in what order it will evaluate a line of code, or are there factors other than operators and their precedence/associativity/arity?

Thanks

  • "rather than the method call itself" - not sure how you imagine assignment of method _call_ (which is not the _result_ of that call). – Sergio Tulentsev Sep 11 '19 at 15:02
  • 3
    Can you imagine what life would be like if the RHS wasn't a "higher precedence" than assignment?! It would make assignments useless. "Rather than the method call itself" -- I'm not quite sure I follow what you mean here. You can assign a method *reference*, but "the call itself" *is* the result of that call. – Dave Newton Sep 11 '19 at 15:05
  • @3limin4t0r I'm just starting to learn programming so maybe I'm contravening some basic rule about what variables are, but it seems possible that every time you called var it could both print 5 and return nil, instead of only returning nil – Ivo Evans Storrie Sep 11 '19 at 15:19
  • 1
    That's not how it works in (approximately) every language. When you assign a value you need to know the value to assign. The value is the RHS of the assignment statement. The RHS will be fully evaluated. For `puts(5)` the evaluation results in the side-effect of a `5` being printed, and the return value is `nil`. The assignment is of the result of the RHS, which is `nil`. That there's a side-effect is irrelevant. – Dave Newton Sep 11 '19 at 15:21
  • Usually _order of evaluation_ problems can be clarified by inspecting the generated AST, I don't know how to do this in ruby :( – geckos Sep 11 '19 at 15:39
  • @geckos: https://github.com/whitequark/parser or RubyVM::AST. – Sergio Tulentsev Sep 11 '19 at 15:41
  • 1
    I just found an way usign RubyVM::AbstractSyntaxTree, I think that ::AST is < 2.6 and ::AbstractSyntaxTree is >= 2.6 – geckos Sep 11 '19 at 15:47
  • [This article](https://medium.com/rubycademy/4-interesting-examples-of-high-precedences-operations-in-ruby-bd9e49dba52b) may be of interest. Surely the precedence of method calls is covered in Ruby's documentation, but I've searched for it unsuccessfully more than once. – Cary Swoveland Sep 11 '19 at 17:29

3 Answers3

5

You're overthinking this. Your expression is basically this: x = something. So, right-hand side must be evaluated first, then the assignment can be done.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
1

I'm guessing you come from a JavaScript or similar background. Where the following is possible:

function puts(...args) { args.forEach(arg => console.log(arg)); }
var x;

(x = puts)(5);
puts(x);

However in JavaScript calling puts without () will return the whole function. Which allows easy function assignment. However in Ruby calling puts without () will still call the method. Making parentheses optional. See the Calling Methods documentation.

In Ruby (x = puts)(5) would result in a syntax error. You can achieve the same by doing the following:

(x = method(:puts)).call(5)
# here parentheses are still required since
x = method(:puts).call(5)
# will still assign the result of the puts call to x

The first link you provided talking about operators having a higher precedence than method calls is talking about method arguments.

puts 5 + 5
# can be seen as
(puts 5) + 5
# or
puts (5 + 5)

In this case 10 is printed since the operators have higher precedence than the method call itself. This also works for the = operator, but when used as argument.

puts x = 5

Will print 5, return nil and have 5 assigned to x. When using x = puts 5, x can't be assigned without evaluating puts 5 so that is what happens first. Precedence only comes into play if the same code could be executed in multiple ways.

Calling methods with parentheses never yields the above issue.

puts(5 + 5)
# or
puts(5) + 5

Both speak for themself. Although the latter will raise a NoMethodError.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
1

Here is how to print AST

2.6.3 :008 > RubyVM::AbstractSyntaxTree.parse('x = puts(5)')
 => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:11> 
2.6.3 :009 > pp _
(SCOPE@1:0-1:11
 tbl: [:x]
 args: nil
 body:
   (LASGN@1:0-1:11 :x
      (FCALL@1:4-1:11 :puts (ARRAY@1:9-1:10 (LIT@1:9-1:10 5) nil))))
 => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:11> 

I'm using ruby 2.6. This way is possible to solve any parsing doubt. For this case is little obvious as other answers said if you have x = expr, then expr need to be evaluated first since we're talking about a strict language, for lazy languages you will only need to evaluate expr when x is evaluated, but this is another topic

geckos
  • 5,687
  • 1
  • 41
  • 53