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¶
- Define
shout wordwithout parentheses. Print whatshout "hi",shout("hi")andputs shout "hi"give. Then assignshout = "quiet"and printshout,shout("hi"),shout "hi"andself.shout("hi")— before running it, predict which of the four reach the method. - Take
puts (2 + 3) * 4and rewrite it three ways that leave no room for doubt —puts((2 + 3) * 4), a temporary variable, and a version withp— then run each spelling underruby -wand 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¶
selfis the implicit receiver — where a receiverless call goes, and whyself.x = vis needed for a setter- The last expression is the return value — why
puts(1+2)*3multiplies nil:putsreturns nil - Blocks are not objects — the other whitespace trap:
puts [1, 2].map do … endbinds the block toputs, not tomap - Warnings and
-w— what the flag does and does not report - Gotchas for Python programmers — this page's rows 7 and 10, in the list of everything else that bites
- Ruby docs: Calling methods ↗ — the reference for receivers, arguments and the spellings on this page
- Common error messages — Twenty-nine errors you will meet in your first month of Ruby, each with the code that raises it, the…
- Twenty-three Python traps for a Ruby programmer — The twenty-three places where a Ruby reflex gives the wrong answer in Python