Am I the only one who doesn't put parentheses around the parameters in Ruby method definitions?
Tags: ruby
Published: 2024-04-12 02:04:04

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.

```ruby
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.

```ruby
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.

![Comparison of method definitions with and without parentheses with parameters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szfcp5eoatr9a792d7f8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/24uye8hffuat9wcdooqf.png)



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)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gftixwxqpgjw5mpolw0x.png)

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


![Method definition without parameters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r62t6a4v9egs4ros59v5.png)

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.

```ruby
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?