Project

General

Profile

Actions

Feature #19107

open

Allow trailing comma in method signature

Added by byroot (Jean Boussier) over 1 year ago. Updated about 1 year ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:110628]

Description

A popular style for multiline arrays, hashes or method calls, is to use trailing commas:

array = [
  1,
  2,
  3,
]

hash = {
  foo: 1,
  bar: 2,
  baz: 3,
} 

Some.method(
  1,
  2,
  foo: 3,
)

The main reason to do this is to avoid unnecessary noise when adding one extra element:

diff --git a/foo.rb b/foo.rb
index b2689a7e4f..ddb7dc3552 100644
--- a/foo.rb
+++ b/foo.rb
@@ -1,4 +1,5 @@
 Foo.bar(
   foo: 1,
-  bar: 2
+  bar: 2,
+  baz: 3
 )

However, this pattern doesn't work with method declarations:

def foo(bar:,) # syntax error, unexpected ')'

Proposal

For consistency and convenience I propose to allow trailing commas in method declarations.


Related issues 2 (0 open2 closed)

Related to Ruby master - Bug #17858: Trailing comma after a `&block` parameter cause a syntax errorClosedActions
Related to Ruby master - Bug #3456: bisarre commaClosedmatz (Yukihiro Matsumoto)06/20/2010Actions
Actions #1

Updated by byroot (Jean Boussier) over 1 year ago

  • Related to Bug #17858: Trailing comma after a `&block` parameter cause a syntax error added
Actions #2

Updated by shyouhei (Shyouhei Urabe) over 1 year ago

Updated by matz (Yukihiro Matsumoto) over 1 year ago

I don't care for consistency here (since formal arguments and actual arguments are different).
I am not sure for convenience. Compare to actual arguments, there's less chance to rewrite/update formal arguments.
Is there an actual case where this proposal is convenient?

Matz.

Updated by byroot (Jean Boussier) over 1 year ago

Is there an actual case where this proposal is convenient?

Yes, when replacing old APIs that took an "option hash" by explicit keyword arguments, it tend to create very large signature.

The last example I have in mind is redis-client: https://github.com/redis-rb/redis-client/blob/dcfe43abb83597bee129537464e20805658bf7a9/lib/redis_client/config.rb#L21-L41

      def initialize(
        username: nil,
        password: nil,
        db: nil,
        id: nil,
        timeout: DEFAULT_TIMEOUT,
        read_timeout: timeout,
        write_timeout: timeout,
        connect_timeout: timeout,
        ssl: nil,
        custom: {},
        ssl_params: nil,
        driver: nil,
        protocol: 3,
        client_implementation: RedisClient,
        command_builder: CommandBuilder,
        inherit_socket: false,
        reconnect_attempts: false,
        middlewares: false,
        circuit_breaker: nil
      )

When adding a new argument, it cause these annoying diffs:

diff --git a/lib/redis_client/config.rb b/lib/redis_client/config.rb
index fc74367..6412171 100644
--- a/lib/redis_client/config.rb
+++ b/lib/redis_client/config.rb
@@ -36,7 +36,8 @@ class RedisClient
         command_builder: CommandBuilder,
         inherit_socket: false,
         reconnect_attempts: false,
-        middlewares: false
+        middlewares: false,
+        circuit_breaker: nil
       )
         @username = username
         @password = password

Also this inconsistency is the reason why some popular styleguides reverted back to not using trailing comma for multi-line enumerations:

Updated by rubyFeedback (robert heiler) about 1 year ago

To me the ',' there looks rather awkward. Then again the
first time I saw def(foo:) I was also confused.

Updated by k0kubun (Takashi Kokubun) about 1 year ago

I second this proposal.

def foo(bar:,) doesn't seem like a real use-case, but when a method has so many arguments and I declare arguments in multiple lines, I would love to put a trailing comma to minimize future git diff and make reviewing the Ruby code easier.

For example, this is today's ERB#initialize:

def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout')
  # ...
end

This line is still short enough to fit on my screen, however, if we were to add another option, e.g. filename, I would write:

def initialize(
  str,
  safe_level=NOT_GIVEN,
  legacy_trim_mode=NOT_GIVEN,
  legacy_eoutvar=NOT_GIVEN,
  trim_mode: nil,
  eoutvar: '_erbout',
  filename: nil,
)
  # ...
end

which doesn't seem awkward to me. But this is a SyntaxError today. If you don't put a , there, you'll see a diff on filename when you add another option after that even if the patch is not related to filename, which would make me frustrated when reviewing that code.

here's less chance to rewrite/update formal arguments.

trim_mode: and eoutvar: are arguments that were added to the method afterward by updating them. attr_accessor :filename already exists in ERB and filename: option in #initialize is a real feature in a competing implementation, Erubi, which has even more options that we might want to add to ERB separately, not just filename:.

Actions

Also available in: Atom PDF

Like5
Like0Like0Like0Like0Like0Like0