DEV Community

Koichi Sasada
Koichi Sasada

Posted on • Edited on

Am I the only one who doesn't put parentheses around the parameters in Ruby method definitions?

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Comparison of method definitions with and without parentheses with parameters

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.

Ratio with and without parenthes 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.

Specific values and the number of files (right axis)

In addition, I also checked what happens when there are no parameters at method definitions.

Method definition without parameters

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
})
Enter fullscreen mode Exit fullscreen mode


`

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)

Collapse
 
bensandeen profile image
BenSandeen

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