I'm Koichi, a Ruby interpreter developer in the Technology Division at STORES, Inc. And I'm looking forward to RubyKaigi 2024!
Now, when you define a method in Ruby, you can omit parentheses even if there are method parameters.
def foo(x, y)
end
def bar x, y
end
That's how you define bar
. I used to prefer to write it this way, but I was shocked when my colleague Mr. Endoh told me that only Koichi (I) uses that way nowadays, so I did some research.
By the way, there is a method definition method that cannot be used without parentheses, so I will tearfully add parentheses at that time.
def foo(kw:) # required keyword argument
end
def bar(&) # unnamed block argument
end
Supplement 1: There is a lot of controversy in Ruby about whether or not to put parentheses when calling a method, but I will only mention the parentheses when defining a method. I haven't heard much about it.
Supplement 2: I think most people don't put parentheses when there are no parameters at definition, so I will describe "only when there are parameters".
Conventions
Rubocop has a default rule that says to put parentheses when there are parameters; even Standardrb has a default ([https://github.com/standardrb/standard/blob/8307fa8f449f896075ccad 74bf6a128ed2c26189/config/base.yml#L1098:title])
https://docs.rubocop.org/rubocop/cops_style.html#stylemethoddefparentheses
The rule at Cookpad, which I feel is often referenced when coding rules, says that brackets MUST be added.
https://github.com/cookpad/styleguide/blob/master/ruby.en.md#method-definitions
[MUST] On method definition, do not omit parentheses of parameter list, except for methods without parameters.
A coding rule that was often referred to in the past also says that the parentheses MUST be added.
https://shugo.net/ruby-codeconv/codeconv.html
Parentheses should be added to the list of temporary arguments in a method definition. However, if there are no arguments, the parentheses should be omitted. (Ja -> En translation)
I'm going to have a hard time getting a job writing programs in Ruby...
Actual survey
I checked how many ways of writing with/without parentheses on Ruby's method definition with parameters in actual Ruby code.
The survey covers the latest versions of all gems pushed to rubygems.org. I am grateful to Endoh-san et, al. using [https://rubygems.org/gems/rubygems-mirror] to manage rubygems mirrors for Ruby development.
It seems that about 95% of the definitions has parentheses (w/paren) and 5% does not (wo/paren).
I thought "don't older gems often have no parentheses?" (Possibility that the rule of putting parentheses has been more thoroughly enforced now than in the past.) So, I tabulated the results by year.
Surprisingly, there was no "more or less because they are older" trend. The percentage is high around the middle.
A bit of caution is that the year counted here is the date of the latest release, not the date of first writing (if the first release was a long time ago and are still releasing in 2024, it is counted in 2024).
Here are the actual values.
In addition, I also checked what happens when there are no parameters at method definitions.
I thought no one put them on, but there are about 3%. Note that there was an anomaly in 2023, so I excluded that (I didn't check what the anomaly was).
Looking at the Rails code, I found that it uses parentheses without parameters when it wants to be written on a single line, like def foo() end
. That kind of thing seems to go away with a one-line method definition (def foo = nil
). No, I'm not so sure. (Write def foo() = nil
?)
Investigation code
I used Prism to investigate.
require 'prism'
class ParenFinder
TYPES = {
true => {
true => :paren_w_params, :paren_w_params, :paren_w_params
false => :paren_wo_params, }
}, }
false => {
true => :noparen_w_params, :noparen_w_params, }
false => :noparen_wo_params, }
}
}
def self.reset
$defs = TYPES.map{_2.values}.flatten.map{[_1, 0]}.to_h
$defs[:files] = 0
$defs[:time] = Time.now.to_i
end
reset
def initialize file
@file = file
end
def defs_rec node
if node.type == :def_node
paren = node.lparen_loc ? true : false
params = node.parameters ? true : false
type = TYPES[paren][params]
$defs[type] += 1 # [@file, node.slice.lines.first.chomp]
else
node.child_nodes.compact.each{|n|
defs_rec n
}
end
end
def defs
ast = Prism.parse_file(@file)
defs_rec ast.value
rescue Exception => e
# ignore any errors
end
If ARGV.empty?
if ARGV.empty?
ParenFinder.new(__FILE__).defs
name = __FILE__
else
process_file = -> f do
case
when FileTest.symlink?
# ignore (depends on data)
when FileTest.directory?(f)
Dir.glob(File.join(f, '*')){|f| process_file.call f}
when /. *\.rb$/ =~ f
$defs[:files] += 1
ParenFinder.new(f).defs
else
# ignore other files
end
rescue Exception => e
# ignore
end
name = ''.dup
ARGV.each{|f|
name << f
process_file.call f
}
$defs[:time] = Time.now.to_i - $defs[:time]
end
pp({ name => $defs.map{|k, v|
if Array === v
[k, v.size].
else
[k, v].
end
})
`
I am glad that I was able to create an easy survey script, although I was able to ask Mr. Endoh, who tinkers with Prism, how to use it. I mean, the lparen_loc
method (the position of the parentheses that wrap the parameters) is amazing.
Symlink handling was going to have to be tweaked for each survey target.
Why method definitions without parentheses are allowed
I had a chat with Yukihiro Matz Matsumoto and asked him, "Why were you allowed to define methods without parentheses?" He said it was for the following reasons.
- In Ruby, parentheses can be omitted in "command-like method" calls. For example,
attr :ivar
. - Matz introduced it because I thought it would be better to be able to define such command-like methods without parentheses as well.
In the discussion, there were opinions such as "The writer cannot judge whether the method is a command-like method or not" and "There seem to be not so many opportunities to write command-like methods". I found out that the function of omitting parentheses when defining a method is likely to be forgotten, except when there are no parameters.
Summary
I started this survey with the question, "Am I the only one who doesn't put parentheses around the parameters in Ruby method definitions?" Is it an interesting result that 5% of them violate well-known coding conventions?
Top comments (2)
Iām too lazy to use my own conventions so I let Rubocop do all the work. Thanks for the interesting post!
Interesting article, thanks for the analysis! Omitting parentheses is one of the "features" I hate most about Ruby, since it adds no value and makes code more difficult to read