Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spacing around parentheses in Ruby

I've recently had some problems getting a path working properly. The solution turned out to be simple but I had an unexpected problem that stopped me getting to it. The following line, included in an erb template, works perfectly:

<%= button_to "Confirm delivery", delivery_confirm_path( @delivery ) %>

This one doesn't:

<%= button_to "Confirm delivery", delivery_confirm_path ( @delivery ) %>

It seems spacing before the opening parenthesis is not allowed in this case. The error I get is this:

/app/views/deliveries/_delivery_buttons.html.erb:22: syntax error,
unexpected tLPAREN_ARG, expecting keyword_do or '{' or '('
...ivery", delivery_confirm_path ( @delivery ) );@output_buffer...
...                               ^

Can anybody explain why this causes an error?

EDIT: for info, this is Ruby 1.9.2 and Rails 3.0.9, on Windows 7 64-bit

like image 812
asc99c Avatar asked Oct 06 '11 13:10

asc99c


2 Answers

Anyone that puts a space before a parameter-list parentheses gets what they deserve, I say!

The problem is that it's closing out the call to button_to in the second example (space before the parentheses) and doesn't know what to do next.

like image 106
Dave Newton Avatar answered Sep 19 '22 02:09

Dave Newton


I'm not sure if this is how ruby's parser actually works, but I think of it this way: The comma before delivery_confirm_path has higher precedence than the parentheses, unless you get rid of the space.

The parser sees the method call as this:

button_to "Confirm delivery", delivery_confirm_path

In other words, delivery_confirm_path is parsed as a method call without arguments. But then the parser sees the dangling ( @delivery ) and it's not valid syntax because it follows the button_to method call. It's as though you had this invalid syntax:

button_to("Confirm delivery", delivery_confirm_path) ( @delivery )

You can avoid the comma-precedence by doing this instead:

button_to "Confirm delivery", (delivery_confirm_path ( @delivery ))

But it's usually easier to just remove the space.

The principle to remember is that if there's a space before parentheses with a method call, the parentheses are used as grouping, and not as method-call parentheses.

Here are some examples to help. I use the following method in my examples:

def foo(*args); puts args.inspect; true; end

If you're on ruby 1.9, I suggest turning on warnings when running the examples: $-w = true. This will display warning: (...) interpreted as grouped expression if you have space before parentheses.

These two lines are syntactically equivalent:

foo (1)
foo 1

That's because (1) as a grouped expression is just 1.

What good is grouping?

One reason is just for more readability. You might consider it easier to understand with parens in this expression versus without:

foo (2 + 3)
foo 2 + 3

Another reasons is precedence. Let's say I have a low-precedence operation, like the and operator. Because the method call has higher precedence, the and is evaluated after the call. This prints [2] and returns 3:

foo 2 and 3  # same as foo(2) and 3, i.e. true and 3

But this prints [3] and returns true:

foo (2 and 3)  # the grouped expr returns 3, which is passed to foo

Note, however that the and example is somewhat contrived, because ruby doesn't allow removal of the preceding space. (I'm not sure why, since && is allowed instead of and.) But you get the idea.

foo(2 and 3)  # syntax error - but why?? I still don't understand.
foo(2 && 3)   # works fine. This is strangely inconsistent.

This demonstrates that removing the space before a method call will elevate the method-call precedence above a comma:

foo 1, foo 2  # syntax error; the 2 is dangling
foo 1, foo(2)  # ok

Another gotcha is argument lists.

foo 2, 3  # both are treated as args to the method call
foo (2, 3)  # syntax error, because "2, 3" is grouped as an expression, but is not a valid one
like image 21
Kelvin Avatar answered Sep 21 '22 02:09

Kelvin