Skip to content

Parentheses are optional, but the space is not

Level: 101 · a first-week reader who writes Python

One line: Ruby lets you leave the parentheses off a call and off a def, so a bare name is a call — and the space before a ( decides whether the parentheses belong to the call or to the expression.

Ruby decides what a bare identifier means while it parses: if an assignment to that name appeared earlier in the same scope, the name is a local variable; otherwise it is a method call on the implicit self. Writing foo() or self.foo always means the method. A def may drop its parentheses too (def f a, b). The price of all this freedom is that whitespace carries meaning: in puts (1+2)*3 the space makes (1+2) a grouped expression, so puts receives 9, while puts(1+2)*3 gives the parentheses to the call, so puts receives 3 and its result — nil — is multiplied. The same goes for a unary minus and a splat: p -1 prints -1 and p - 1 subtracts from the result of p. The -w flag warns about the minus and the star, but says nothing about the grouped expression.

Verified output of parentheses_are_optional_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

1. a call without parentheses:    puts "x"        -> prints x
2. a def without parentheses:     def f a, b       -> f 1, 2 = 3
3. a bare name, no local exists:  greeting         -> "the method"
4. after greeting = "the local": greeting       -> "the local"
5. parentheses force the method:  greeting()       -> "the method"
6. a receiver forces it too:      self.greeting    -> "the method"
7. one space before the paren:    puts (1+2)*3     -> prints 9
8. no space before the paren:     puts(1+2)*3      -> prints 3
   then NoMethodError: undefined method '*' for nil
9. ruby -w on row 7:              stdout "9", stderr "" (no warning in 4.0)
10. minus, one space:             p -1             -> prints -1
11. ruby -w on row 10:            -e:1: warning: ambiguous first argument; put parentheses or a space even after `-` operator
12. minus, two spaces:            p - 1            -> NoMethodError: undefined method '-' for nil
13. star, one space:              rest *[1, 2]     -> [1, 2]
14. ruby -w on row 13:            -e:1: warning: ambiguous `*` has been interpreted as an argument prefix

Reading the output

Rows 1 and 2 are the plain freedom: puts "x" and def f a, b both parse. Rows 3 to 6 are the rule for a bare name: with no local called greeting, the bare name calls the method; once greeting = "the local" has run, the bare name is the variable, and only greeting() or self.greeting reaches the method again.

Rows 7 and 8 differ by one space. puts (1+2)*3 prints 9: the parentheses group 1+2 and the whole product is the argument. puts(1+2)*3 prints 3 and then raises NoMethodError: undefined method '*' for nil, because the parentheses were the argument list, puts returned nil, and nil * 3 has nowhere to go. Row 9 starts a child Ruby with -w on row 7's spelling and captures an empty stderr: the 4.0 parser accepts the grouped expression without comment, so the flag will not catch this one for you.

Rows 10 to 12 do the same with a minus. p -1 is p(-1); row 11 shows that -w does warn here — "ambiguous first argument; put parentheses or a space even after - operator" — and p - 1 is p() - 1, which is nil - 1. Rows 13 and 14: rest *[1, 2] is a splat argument, again with a -w warning, whereas rest * [1, 2] would be multiplication.

Compared with Python

Python has no bare-name calls: parentheses are the call operator, a name is a reference, and whitespace before ( is never significant. So print "x" and def f a, b: fail to compile (rows 1 and 2), the bare greeting is a function object (row 3), and once the name is rebound to a string, greeting() is a TypeError because the function is simply gone (rows 4 to 6). Rows 7 and 8 are the interesting pair: print (1+2)*3 and print(1+2)*3 are the same program in Python — both print 3 and then fail on None * 3 — and row 9 proves it by compiling both spellings to identical bytecode. Ruby's row 8 and Python's rows 7 and 8 are the same mistake with the same shape of error: a printing function returns nothing, and arithmetic on nothing fails. Rows 10 to 14 close the loop: p -1 and p - 1 are both subtraction in Python, and * unpacks only inside the call's parentheses.

Verified output of parentheses_are_optional_py.py — regenerated by tools/run_examples.py, never hand-typed.

1. a call without parentheses:    print "x"        -> SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
2. a def without parentheses:     def f a, b:      -> SyntaxError: expected '('
3. a bare name, no local exists:  greeting         -> a function object, not a call
4. after greeting = "the local": greeting       -> 'the local'
5. parentheses call the binding:  greeting()       -> TypeError: 'str' object is not callable
6. a receiver forces it too:      self.greeting    -> (no counterpart: the function was overwritten by row 4)
7. one space before the paren:    print (1+2)*3    -> prints 3
   then TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
8. no space before the paren:     print(1+2)*3     -> prints 3
   then TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
9. rows 7 and 8 compile to the same bytecode: True
10. minus, one space:             p -1             -> TypeError: unsupported operand type(s) for -: 'builtin_function_or_method' and 'int'
11. python3 -W error on row 10:   nothing to warn about: the space never changes the parse
12. minus, two spaces:            p - 1            -> TypeError: unsupported operand type(s) for -: 'builtin_function_or_method' and 'int'
13. star, one space:              rest *[1, 2]     -> TypeError: can't multiply sequence by non-int of type 'function'
14. the star unpacks only inside the call's parentheses: rest(*[1, 2]) -> (1, 2)

Try it

  1. Define shout word without parentheses. Print what shout "hi", shout("hi") and puts shout "hi" give. Then assign shout = "quiet" and print shout, shout("hi"), shout "hi" and self.shout("hi") — before running it, predict which of the four reach the method.
  2. Take puts (2 + 3) * 4 and rewrite it three ways that leave no room for doubt — puts((2 + 3) * 4), a temporary variable, and a version with p — then run each spelling under ruby -w and note which ones the flag warns about (this page says: none of them, unless a unary minus or a star is involved).
Solution to 1

parentheses_are_optional_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Kata: define `shout word` without parentheses, then shadow it with a local
# variable and show which spelling still reaches the method.
def shout word
  word.upcase + "!"
end

puts "1. shout \"hi\"             -> #{(shout "hi").inspect}"
puts "2. shout(\"hi\")            -> #{shout("hi").inspect}"
print "3. puts shout \"hi\"        -> prints "
puts shout "hi"
shout = "quiet"
puts "4. shout = \"quiet\"; shout  -> #{shout.inspect}  (the local wins)"
puts "5. shout(\"hi\")            -> #{shout("hi").inspect}  (parentheses reach the method)"
puts "6. shout \"hi\"             -> #{(shout "hi").inspect}  (a bare name with an argument is a call)"
puts "7. self.shout(\"hi\")       -> #{self.shout("hi").inspect}  (a receiver reaches the method)"

Verified output of parentheses_are_optional_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

1. shout "hi"             -> "HI!"
2. shout("hi")            -> "HI!"
3. puts shout "hi"        -> prints HI!
4. shout = "quiet"; shout  -> "quiet"  (the local wins)
5. shout("hi")            -> "HI!"  (parentheses reach the method)
6. shout "hi"             -> "HI!"  (a bare name with an argument is a call)
7. self.shout("hi")       -> "HI!"  (a receiver reaches the method)

See also