Project

General

Profile

Actions

Feature #19107

closed

Allow trailing comma in method signature

Feature #19107: Allow trailing comma in method signature
8

Added by byroot (Jean Boussier) over 3 years ago. Updated about 3 hours ago.

Status:
Closed
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 3 (1 open2 closed)

Related to Ruby - Bug #17858: Trailing comma after a `&block` parameter cause a syntax errorClosedActions
Related to Ruby - Bug #3456: bisarre commaClosedmatz (Yukihiro Matsumoto)Actions
Related to Ruby - Feature #21875: Handling of trailing commas in lambda parametersOpenActions

Updated by byroot (Jean Boussier) over 3 years ago Actions #1

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

Updated by shyouhei (Shyouhei Urabe) over 3 years ago Actions #2

Updated by matz (Yukihiro Matsumoto) about 3 years ago Actions #3 [ruby-core:111113]

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) about 3 years ago Actions #4 [ruby-core:111122]

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 3 years ago Actions #5 [ruby-core:112393]

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 3 years ago Actions #6 [ruby-core:112394]

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:.

Updated by k0kubun (Takashi Kokubun) about 1 month ago Actions #7 [ruby-core:124520]

Can we reconsider this? To Matz' question (see also: DevMeeting-2022-12-01):

Is there an actual case where this proposal is convenient?

@byroot (Jean Boussier) and I have shown real-world use cases in open-source repositories. In closed-source applications, e.g. at Shopify, it's common to see methods that have much more (optional, keyword) arguments, which would benefit from this syntax.

At the Ruby Release 30th Party, in Koichi's presentation (which Matz missed), @byroot (Jean Boussier) mentioned this feature as one of his wishes for the future of Ruby. IIRC, @shyouhei (Shyouhei Urabe) said this was rejected last time because this would cause a conflict in the syntax when parentheses are omitted. However, somebody else (I forgot, maybe @yui-knk (Kaneko Yuichiro) or @nobu (Nobuyoshi Nakada)?) said some discrepancy between method definitions with parens and ones without parens already exist, and I'm sure we want this feature only when a method is defined with parens. So we shouldn't need to reject this for the previous reason.

So, can we revisit this? Now that it is in, this is my most-wanted feature in Ruby now. I respect that there are people who don't put parens in method definitions (and therefore have no interest in this feature) or think trailing commas are awkward, but I'd like to remind you that there are other people who put parens in method definitions and want to minimize the git diff when a new parameter is added at the end of such method definitions with many parameters.

Updated by matz (Yukihiro Matsumoto) 11 days ago Actions #8 [ruby-core:124775]

I've been persuaded. I'll accept it. It's better not to allow commas to appear after certain points (like after &block and ...).

Matz.

Updated by Earlopain (Earlopain _) 11 days ago Actions #9 [ruby-core:124781]

  • Assignee set to prism

To clarify, to we also accept -> (a, b, ) {}? I would say yes but am going to assume no for now.

Updated by byroot (Jean Boussier) 11 days ago Actions #10 [ruby-core:124782]

From the meeting logs:

nobu: carriage return is required? def foo(a,) seems strange.
matz: no problem to allow it.

So yes.

Updated by byroot (Jean Boussier) 11 days ago Actions #11 [ruby-core:124783]

Note that Matz also pointed out that (...,) and (&block,) should still not be allowed.

Updated by Earlopain (Earlopain _) 11 days ago Actions #12 [ruby-core:124789]

Ah, sorry. That was about lambdas, not newlines.

I'll wait until the logs are published and see if they came up. They were never mentioned explicitly but it should be very easy for prism to allow them if that changes.

Updated by byroot (Jean Boussier) 11 days ago Actions #13 [ruby-core:124790]

They weren't mentioned, but since they use () like methods, I think it is implied it works for them too?

Updated by Earlopain (Earlopain _) 11 days ago Actions #14 [ruby-core:124793]

Hm, not so sure about lambdas actually. Consider |foo,| block arguments, which causes the block to accept any number of positional arguments. I think a lambda should behave the same.

If it is so, the usefulness is pretty limited since it doesn't work with the way the issue is about. Either way, I think it would need to be discussed first since I'm not 100% on the desired behaviour.

Updated by byroot (Jean Boussier) 11 days ago Actions #15 [ruby-core:124794]

I'm no expert on that area, but my understanding is that lambda specifically handle argument like methods do, not like proc do.

Updated by Earlopain (Earlopain _) 11 days ago 1Actions #16 [ruby-core:124795]

->(...) {} is syntax invalid, same as foo do |...|; end, in that regard they are more like blocks. Lambdas do check arity though, which is probably what you were talking about. I'll make a separate issue for this to understand what the desired behavior should be if that's ok.

Updated by Earlopain (Earlopain _) 11 days ago Actions #17

  • Related to Feature #21875: Handling of trailing commas in lambda parameters added

Updated by Earlopain (Earlopain _) 11 days ago Actions #18 [ruby-core:124798]

https://github.com/ruby/prism/pull/3920 for the prism implementation. I plan to add https://bugs.ruby-lang.org/issues/21875 to the next devmeeting.

Updated by Earlopain (Earlopain _) about 22 hours ago Actions #19

  • Status changed from Open to Closed

Applied in changeset git|731b6092e9268af960bf0f060e37504f4deb7380.


[ruby/prism] [Feature #19107] Allow trailing comma in method signature

https://github.com/ruby/prism/commit/b7e247ce6a

Updated by nobu (Nobuyoshi Nakada) about 10 hours ago Actions #20 [ruby-core:124867]

  • Assignee changed from prism to core

What about the lambda arguments?
731b6092e9268af960bf0f060e37504f4deb7380 seems not to allow ->(a,) {}.

Updated by Earlopain (Earlopain _) about 3 hours ago Actions #21 [ruby-core:124868]

What about the lambda arguments?

@nobu (Nobuyoshi Nakada) it's unclear to me so I didn't change them. I added https://bugs.ruby-lang.org/issues/21875 to the next dev meeting.

Actions

Also available in: PDF Atom