Feature #19107
closedAllow trailing comma in method signature
Added by byroot (Jean Boussier) over 3 years ago. Updated about 9 hours ago.
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.
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
- Related to Bug #3456: bisarre comma added
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) 12 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 _) 12 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) 12 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) 12 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 _) 1 day ago
Actions
#19
- Status changed from Open to Closed
Applied in changeset git|731b6092e9268af960bf0f060e37504f4deb7380.
[ruby/prism] [Feature #19107] Allow trailing comma in method signature
Updated by nobu (Nobuyoshi Nakada) about 16 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 9 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.