SE-0439: Allow trailing comma in comma-separated lists

This feels like the wrong direction for Swift to be taking. The illusion of stylistic choice seems nice (and everyone who has their own particular style would probably fight a very prolonged battle on their stylistic hill), but there is a fantastic freedom that comes from using an opinionated language that makes stylistic decisions for you.

Want to use curly braces? Too bad, you can't in python. What to have trivial if statements on a single line in Go? Too bad, the compiler reformats your code.

It takes a bit of getting used to, but it also makes the overall consistency of the code so much more pleasant to use and grok for newcomers. I would love to see Swift move in that direction, even if it means that I have to give up my personal stylistic preferences.

8 Likes

Absolutely +1. Makes diffs easier, makes adding and removing items easier, and makes alternating between programming in Swift and Rust easier on my poor little smoothbrain.

9 Likes

I follow along with many Swift Evolution proposals and am rather active in the community and don't comment very often, but I felt rather compelled to chime in on this with a different perspective.


  • What is your evaluation of the proposal?

A pretty strong -1. I'd like to take a few examples from the proposal itself of what wouldn't be allowed to illustrate why I don't consider this is a positive change.

enum E {
  case a, b, c, // ❌ Expected identifier after comma in enum 'case' declaration
}

protocol Foo {
  associatedtype T: P1, P2, // ❌ Expected type
}

(1,) // OK
(,) // ❌ expected value in tuple

Based on the examples above and the quote below, I believe the mental model for understanding when a trailing comma is allowed or not allowed is rather complex.

Trailing commas will be supported in comma-separated lists whenever there is a terminator clear enough that the parser can determine the end of the list. The terminator can be the symbols like ), ], >, { and :, a keyword like where or a pattern code like the body of a if, guard and while statement.

I'm not cherry-picking examples or trying to construct a straw-man argument, these selections were taken in succession directly from the proposal itself.


The mental model for when a trailing comma is allowed may be clear to a language designer, compiler engineer, or from a parser perspective, but as a developer who uses the Swift language and has been writing Swift every day for the last 10 years I find the rules and exceptions to be pretty confusing.

I have a hard time imagining a novice developer, perhaps you're a student or a junior developer, looking at Swift code and thinking these rules are consistent. Swift may be your first language or this is the first time you're writing Swift at a new job, and it's not a logical leap to imagine you coming away unsure of why trailing commas work in some places and don't in others.

My belief is that there is little benefit being gained here in a way that that necessitates that tradeoff. If this feature were something akin to a complex generics feature then I would understand the need for different syntax to represent a complex concept, but given this is going to be something every single developer encounters, I do not think it's a worthwhile change.

To make an argument by analogy, the ++ operator was removed from the language because it's not immediately obvious how it works to someone who hasn't written a lot of code. While I don't think we should make every decision based on that logic, I found that argument somewhat compelling for what Swift says it strives to be, and find this change to be a pretty strong 180 from that logic.


  • Is the problem being addressed significant enough to warrant a change to Swift?

I'm open to being convinced this is a problem worth solving, but I don't think so. I won't deny the fact that it will make prototyping easier, but I generally agree with some comments above which note that this change prioritizes writability over readability.


  • Does this proposal fit well with the feel and direction of Swift?

Ultimately the only people who can decide this are the core team because they determine what fits the feel and direction of Swift, but it's not something I would like in my platonic ideal of a Swift programming language.

For example I'm much less opposed to trailing commas in collection literals, a choice that was made in Swift 1.0. I don't find that choice to be consistent with the language's emphasis on directness over indirectness, but you could make an argument that arrays are unbounded in nature so it's ok there. (Like I said, I'm open-minded and not trying to be difficult.)

I generally think the solution to an exception is figuring out an abstraction that makes the exception more clear or more explicit, not expanding other surface areas of a problem to be open to exceptions.


  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

As far as I know I haven't used a language that supports this feature but perhaps I'm wrong. (Swift, Objective-C, Java, Go, Ruby)


  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read through this proposal and then re-read it to make sure I had properly grasped everything before leaving a comment.

13 Likes

Although this change won't impact existing valid code it will change how some invalid code is parsed.

Is this definitely the case? What about in a result builder context that allows expressions of Void type?

@VoidAllowingBuilder
func foo() {
  if conditionA,
     conditionB,
  {
    print("Hello")
  }
  ()
}

This should currently produce a call to buildOptional(_: Void) followed by buildExpression(_: Void). Would this proposal not instead turn this into a single buildOptional(_: Void) call?

1 Like

+1. I use (and mandate via SwiftFormat) trailing commas in multiline collection literals and find the inconsistency of not being able to use them at function call sites irritating. Juggling commas is also just another unnecessary task to perform when reordering function parameters.

I also work in Kotlin where trailing commas are used in multiline calls. I like their effect on diffs when doing code review.

The places where trailing commas would not be allowed in this proposal look pretty OK to me.

I don't have a strong opinion on trailing commas at single line call sites. I don't love them, but I'm fine with allowing them in the language.

10 Likes

The place where I want this every day is in parameter list in the call site. I think that will bring alignment with literals and facilitate the most common style adopted by the language nowadays.

But I would lie if I said I was happy with the other changes in the proposal. A lot of them feel very weird and like broken code, specially in the declaration side.

So I’m very +1 but just for one of the proposed changes.

Also is interesting that the proposal mentions how the syntax if closing parens in next line is common yet a lot code coming from wwdc doesn’t do that, which I find weird every time I read it :stuck_out_tongue_winking_eye:

4 Likes

+1 - I‘m all for it. As someone who always uses self, I wasn‘t a fan of if let foo = self.foo which is a shortcut that hides semantics. Trailing commas though are also a shortcut, but they don‘t hide anything, they just make diffs and editing nicer.

6 Likes

How about only allowing a trailing comma if it’s the last thing on a line?

This would at least make some of the weirder examples from the proposal invalid, e.g.:

(1, 2,)
let block: (Int, Int,) -> Void = { (a, b,) in  }
let (a, b,) = (1, 2,)
for (a, b,) in zip(s1, s2) { }
func foo(a: Int, b: Int,) { }
foo(a: 1, b: 1,)

AFAICT, the main motivation for this proposal is to make it easy to comment out lines. When you do that for the last line, it currently makes the second-to-last line invalid code.

I’m pretty sure this will not be seen as a viable alternative since it’s inconsistent with the existing trailing comma in collection literals. I also haven’t thought through all cases, so this probably doesn’t fix all use cases.

I’m sorry if this has been discussed and dismissed in the pitch phase already and I missed it.

4 Likes

I’m not sure about this. Could it be a warning ? So we can iterate on code but we lean toward readability ?

In your theory, would you suggest disallowing trailing commas also in array literals because they may let you think there are more elements missing?
I don't think so.


Anyway I agree to this proposal due to the consistency between array literals and other list expressions.
My use-case would be the same with @Paul_Cantrell's example.

protocol MyProtocol {}

func takeArray(_ a: [any MyProtocol]) {}
func takeParamPack<each T>(_ t: repeat each T) where repeat each T: MyProtocol {}

takeArray([
  foo,
  bar,
  baz,
])

takeParamPack(
  foo,
  bar,
  baz, // Current Swift disallows this trailing comma 😢
)
1 Like

+1

I think it is a net positive for productivity for the compiler not to reject this code whose meaning is unambiguous.

That said, I hope swift-format will offer options to customise where trailing commas are allowed.

9 Likes

That's parses fine. The only problem that we found during implementation was he particular one of a block followed by another block, which is invalid code anyway.

That's fair, I will update them. Thanks for pointing it out.

2 Likes

I would like to point out that while I personally agree that some use cases are strange, that's our personal experience. It's reasonable to assume that someone not voicing their experience here may work in a way that they see the same advantages we see for call sites and condition lists in these strange cases.

While we could enable trailing comma only for these more common cases it's important to note that this would leave us in a place that one could latter come up with really good examples for what we see strange today, we would have another proposal for very particular cases and so on.

That's why the proposal goes in the direction of consistently allowing trailing comma whenever possible, with no code style judgements.

7 Likes

For me, there is a fundamental difference between the Dictionary/Array case and the proposal we have here. The difference to me comes down to:

  1. Dictionary/Array can have an arbitrary number of elements, that are likely to change/grow over time. For the cases proposed here, adding elements to functions/initializers/subscripts/etc is a structural change: its signature changes. This for me is counter intuitive: I expect these to remain mostly stable, whereas the trailing comma signifies either that it'll probably change or I forgot something.
  2. While adding elements to a Dictionary/Array keeps the type the same, adding elements to a tuple changes its type.
  3. Ambiguity for consumers of an API. What does this mean: Vector2D(x: 1, y: 2,) or Vector3D(x: 1, y: 2, z: 3,) ?
    So to summarize: the trailing comma in the cases suggested by the proposal signals "more to come" for me, whereas for these cases I expect stability. This is different from the Array/Dictionary cases.

As for the proposal:

  • What is your evaluation of the proposal?
    -1

  • Is the problem being addressed significant enough to warrant a change to Swift?
    I do see that this solves a need - in particular quickly commenting in/out of lines. But for me this issue is not big enough when I compare it to how I anticipate tripping over this feature. Especially as the consumer of an APIs.

  • Does this proposal fit well with the feel and direction of Swift?
    I don't think it fits very well: it helps make a specific actions easier (i.e. commenting in/out lines) while sacrificing clarity and signaling intent.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
    No

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    Quick reading, in particular the examples.

9 Likes

That's why the proposal goes in the direction of consistently allowing trailing comma whenever possible, with no code style judgements.

This I do not agree with. It is perfectly valid for a language to be opiniated about code style. I'd say its actually an advantage: Swift has clear guidelines about naming things, creating a clear idiom. One of the things I like most about Swift.

8 Likes

What is your evaluation of the proposal?

+1 based upon the same points mentioned earlier:

  • More consistency with arrays and dictionaries
  • Better diff-ability
  • Easier prototyping without comma juggling

One thing I see missing from the proposal is any potential impact, or lack of impact, to compiler performance due to this change. I'm assuming little impact but my opinion could change if that assumption doesn't hold.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

This proposal seems similar to what I've used in Kotlin and Ruby and the rules seem fairly intuitive, although I haven't used the feature so that is hard to judge.

I will note that I believe I have hit unexpected edge cases in Ruby around places where trailing commas were not allowed but I expected them to be – this happened long enough ago that I have a vague recollection but don't remember the details. I've just tried to search for documentation around the Ruby behavior and could not find anything useful so I'd hope there would be good documentation around this feature in Swift that we could point folks to. "How this will be documented" might be an interesting thing to include in the proposal.

I also regularly see these types of trailing commas in reviews of Kotlin code and get jealous when I do.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Quick reading of the proposal and reading of all the comments.

2 Likes

I think having both styles in the proposal is important.

Since readability is an important consideration for this proposal, it's important that it includes a full range of examples that show how this proposal affects readability and where this proposal might introduce things that 'feel out of place'.

Otherwise, the proposal isn't giving the full picture of its effect to readers and reviewers.

3 Likes

As a counter-point, I very often write test harness and helper code. Early versions might take an array of tuples of inputs and expected outputs. It's common for me to refactor them to take a variadic parameter instead, which can be cleaner.

Example

4 Likes

I am an avid user of trailing commas, but I would never use them on a single line. Code like

let list = [1, 2, 3,]
let dict = [1: 2, 3: 4,]

reads very unpleasantly to me. I have never written code like that nor seen code like this. The selling point of trailing commas, at least to me, is purely in the multiple-lines scenario:

let list = [
    1,
    2,
    3,
]
let dict = [
    1: 2,
    3: 4,
]

For consistency, the single-line scenario should still compile with trailing commas, just like a lot of other strangely formatted code compiles just fine. But I do not expect to write or see code like that in practice.

26 Likes