skip to content

In Ruby, when does omitting parentheses on a call, or putting a space before them, change which object a chained method runs on?

level: middleimportance: should knowfreq 35%

answer

  1. poetry mode takes the rest as arguments
  2. chaining on a result needs parentheses
  3. space turns ( into grouping
  4. nested paren-less calls: SyntaxError
  5. ambiguous first argument warning

basics

~20 s

Without parentheses, everything after the method name is its argument list, so format_title raw.upcase upcases raw, not the result. A space before the parenthesis makes it a grouping inside the argument, so format_title (raw).upcase behaves the same way; format_title(raw).upcase chains on the result.

solid answer

~40 s

Calling without parentheses, sometimes called **poetry mode**, makes Ruby read the rest of the expression as the argument list. So `format_title raw.strip.upcase` passes `raw.strip.upcase` as the argument; nothing is chained onto `format_title`'s result. To chain on the result you need `format_title(raw).upcase`. The space matters too: `format_title (raw).upcase` is parsed as `format_title((raw).upcase)`, because a parenthesis after a space starts a grouped expression, not an argument list. Paren-less calls also cannot nest with several arguments: `label product, format_title raw, 60` is a `SyntaxError`. In practice Ruby style omits parentheses for statement-like calls such as `puts`, `require`, `raise` and DSL methods, and writes them whenever the result is chained or the call sits inside another call's arguments.

code

ruby · 9 lines
ruby
def format_title(text)
  text.strip.capitalize
end

raw = " red mug "

format_title(raw).upcase    # => "RED MUG"   upcase on the result
format_title raw.upcase     # => "Red mug"   upcase on raw, then capitalize
format_title (raw).upcase   # => "Red mug"   space: (raw).upcase is the argument

go deeper

for a junior

Recall that without parentheses the rest of the line is the argument list, so chaining on a call's result needs parentheses.

for a middle

Explain the three spellings, especially why a space before the parenthesis turns it into a grouping, and why nested paren-less calls with several arguments do not parse.

for a senior

Catch these bugs in review, where the code looks right, and set lint rules so the ambiguous forms are flagged automatically.

for a principal

Pick a parentheses policy for the codebase that balances DSL readability against unambiguous chaining, and keep it enforced by tooling rather than taste.

## What "poetry mode" means Ruby lets a method call drop its parentheses: `puts title` is the same call as `puts(title)`. This paren-less style is nicknamed **poetry mode**. It reads well for statement-like calls, but it changes how a chain is grouped, because **without parentheses the rest of the expression is taken as the argument list**. ## Three spellings, three meanings Consider a helper `format_title(text)` that returns a String, and `raw = " red mug "`: | Code | Parsed as | `upcase` runs on | |---|---|---| | `format_title(raw).upcase` | `(format_title(raw)).upcase` | the helper's result | | `format_title raw.upcase` | `format_title(raw.upcase)` | `raw` | | `format_title (raw).upcase` | `format_title((raw).upcase)` | `raw` | 1. With parentheses touching the name, the argument list ends at `)` and `.upcase` is chained onto the returned value. 2. Without parentheses, `raw.upcase` is one argument expression, evaluated before the call. 3. With a **space** before `(`, the parenthesis is a **grouping** that starts the first argument, so the whole `(raw).upcase` is the argument. The code looks like case 1 and behaves like case 2. RuboCop's `Lint/ParenthesesAsGroupedExpression` flags a call such as `format_title (raw)`, where a space separates the name from what looks like its argument list; its documentation deliberately accepts forms such as `do_something (foo * bar).baz`, where the grouping is evidently intended. ## Nesting paren-less calls The parser cannot tell which arguments belong to which method when paren-less calls with several arguments are nested. The core documentation's example, `method_one argument1, method_two argument2, argument3`, is a `SyntaxError`. In the product-title domain: ```ruby label product, format_title raw, 60 # SyntaxError label product, format_title(raw, 60) # clear ``` RuboCop's `Style/NestedParenthesizedCalls` asks for parentheses on a call nested inside another parenthesized call's arguments, as in `puts(format_title raw)`. ## Ambiguous first arguments Some characters mean one thing as an operator and another as a prefix, so a paren-less call whose first argument starts with one is ambiguous: - `puts -discount`: minus as a unary operator on the argument, or `puts - discount`? Ruby picks the argument reading and, only in verbose mode, warns about an ambiguous first argument, suggesting parentheses or a space after the minus. - `p *titles`: splat or multiplication? Ruby treats it as a splat and, only in verbose mode, warns that the ambiguous `*` was interpreted as an argument prefix. - RuboCop's `Lint/AmbiguousOperator` reports the same cases. ## The working convention - **Omit** parentheses for statement-like calls whose result is not used further: `puts`, `require`, `raise`, `attr_reader`, and DSL methods such as a test framework's `describe` or `it`. - **Write** them for calls with arguments whose result is chained, compared or passed on, and for any call nested inside another call's arguments. - **Drop** empty ones: `title.strip()` is legal, but RuboCop's `Style/MethodCallWithoutArgsParentheses` (enabled by default) asks for `title.strip`. - RuboCop's `Style/MethodCallWithArgsParentheses`, which would demand parentheses everywhere, is **disabled by default**; teams that want a stricter rule enable it. How a block binds when parentheses are omitted, `{}` versus `do...end`, is a related trap that belongs to blocks rather than to chaining. ## Reading suspicious code When a paren-less call appears in a chain, rewrite it mentally with explicit parentheses before trusting it: 1. Find the method name that has no `(` touching it. 2. Everything after it, up to the end of the expression or the next comma at that level, is its argument list. 3. Any `.method` inside that span belongs to the argument, not to the call's result. Applied to `format_title raw.strip.upcase`, the mental rewrite is `format_title(raw.strip.upcase)`, and the question "is the result upcased?" answers itself: no, the input is. The same habit catches the space-before-parenthesis case, because `format_title (raw).upcase` rewrites to `format_title((raw).upcase)` once you remember that a spaced parenthesis is a grouping.

  • Why does `format_title raw.upcase` return "Red mug" rather than "RED MUG"?
    Because the argument is `raw.upcase`, computed first, so the helper receives `" RED MUG "`. It then strips it and calls `capitalize`, which upcases the first character and downcases the rest, producing `"Red mug"`. The `upcase` never touched the helper's result, which is the whole poetry-mode trap.
  • When is a paren-less call still the idiomatic choice?
    For statement-like calls whose return value is not used, such as `puts`, `require`, `raise` and `attr_reader`, and for DSL methods that read like declarations. As soon as the result is chained or the call is an argument of another call, parentheses make the grouping explicit.

saying these in an interview costs you the question

  • format_title raw.upcase upcases the value format_title returns.
  • A space between a method name and ( is purely cosmetic.
  • Paren-less calls can be nested freely inside other paren-less calls.
  • RuboCop requires parentheses on every call with arguments by default.
  • puts -x always prints a warning in normal runs.