Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the result of applying the minus operator depend on newline characters surrounding it?

Tags:

ruby

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)

like image 456
Marc Ihm Avatar asked Nov 21 '25 08:11

Marc Ihm


1 Answers

This has nothing to do with arithmetic.

In Ruby, there are two ways of passing an argument list to a message send:

  • Whitespace and
  • Parentheses.

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:

  • Expression grouping and
  • Argument lists.

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:

  • Keywords or
  • Expression separators, and expression separators can either be
    • Semicolons (;) or
    • Linebreaks.

So, 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:

Example 1

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) ))

Example 2

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:

  • Evaluate the expression x.
  • Do nothing with the result, do not store it in a variable, pass it as an argument, etc. – in other words, just throw the result away.
  • Evaluate the expression y. Let's call the resulting object of that evaluation E1.
  • Send the message -@ with no arguments to the object E1. Let's call the resulting object of that message send E2.
  • Evaluate self (the implicit receiver). Let's call the resulting object of that evaluation E3.
  • Send the message 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)
like image 106
Jörg W Mittag Avatar answered Nov 24 '25 22:11

Jörg W Mittag



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!