If I run this very simple program:
x = 1
y = 2
puts ( x -
y )
puts ( x
- y )
puts ( x - y )
I get this output (!):
-1
-2
-1
However, I would expect this to print:
-1
-1
-1
I.e., -1 three times.
What’s happening here, i.e., how can the second puts be parsed so wrong? (tried with Ruby 3.3.7)
This has nothing to do with arithmetic.
In Ruby, there are two ways of passing an argument list to a message send:
So, you can either do
foo.bar baz, quux
or
foo.bar(baz, quux)
and both are passing the argument list bar, quux to the message send which sends the message named bar to the object returned by evaluating the expression foo.
However, in Ruby, parentheses are used for two different purposes:
Remember that we said above that you can use either whitespace or parentheses to pass an argument list to a message send. So, what if you use whitespace and parentheses? Parentheses are an argument list if and only if they directly follow the message name in a message send with no whitespace. I.e., this is an argument list:
foo.bar(baz)
This is just expression grouping:
foo.bar (baz)
In your case, there is always whitespace between the message name puts and the opening parenthesis, so all of these are expression groups, not argument lists.
The third Ruby principle we need to know is that expressions can be separated using:
;) orSo, for example, I can write
if condition then consequence else other_consequence end
or
if condition; consequence else other_consequence end
or
if condition
consequence else other_consequence end
Of course, the idiomatic way to write this would rather be:
if condition
consequence
else
other_consequence
end
If we put all of these three things together, we can analyze your three examples like this:
puts ( x -
y )
In this case, because there is whitespace after the message name, the parentheses do not denote an argument list, they denote expression grouping. In other words, this is equivalent to
puts(( x -
y ))
The linebreak in this case does not get interpreted as an expression separator because there is a binary infix operator at the end of the line, so Ruby knows that there must be another operand coming on the right side of that binary infix operator.
In other words, this is not interpreted as
puts(( x -; y ))
(which would be a SyntaxError) but rather interpreted as
puts(( x - y ))
[Note that this is the same as Example 3.]
which in turn is just syntactic sugar for sending the message - with argument list y to the result of the expression x:
puts(( x.-(y) ))
puts ( x
- y )
Here, again, because of the whitespace between the message name puts and the opening parenthesis, the parentheses are not interpreted as delimiters for an argument list but rather as expression grouping:
puts(( x
- y ))
However, in this example, there is nothing telling Ruby that the expression continues on the next line, so the linebreak is interpreted as an expression separator, and the code is thus equivalent to this:
puts(( x; - y ))
Ruby allows whitespace around operators, so - y is the same as - y and also the same as -y:
puts(( x; -y ))
-y is syntactic sugar for sending the message named -@ to the result of the expression y with an empty argument list:
puts(( x; y.-@() ))
So, what this says is:
x.y. Let's call the resulting object of that evaluation E1.-@ with no arguments to the object E1. Let's call the resulting object of that message send E2.self (the implicit receiver). Let's call the resulting object of that evaluation E3.puts with the single argument E2 to the object E3.Or, to make a long story short: because evaluating a local variable has no side-effects and you are not storing the result of that evaluation or pass it as an argument, your code is really just equivalent to:
puts(-y)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With