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

+1 from me. I run into compiler complaints from commenting out code or from using Swift’s pre-processor-like conditions #if COND, not so infrequently. Both when using array literals and when declaring protocol conformances for types. It would be nice if the last item in most lists could have a comma, same as any other item.

I do not find it a major issue that there are places where trailing commas can’t be supported, based on the ones identified in the proposal.

5 Likes

-1 to ever allowing eliding comas.

4 Likes
  • What is your evaluation of the proposal?
    Strong -1

Many of the examples read very poorly to me (although forcing the 'clear seperator' to be a newline might help somewhat.) I can't look past the comma as significant punctuation, and worry about how it might read to newcomers in its various forms - is a trailing comma in arguments syntax sugar for a nil final parameter? Is it some sort of weird syntax for variadics? Is it some sort of wildcard in pattern matching?

Relatedly, are there other language features we might want to add in the future that end up difficult or inelegant because of this weird syntactic escape hatch?

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

In my experience, no, my time spent shifting commas is practically invisible compared to other time sinks.

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

No. Allowing code to be littered with commas 'just in case' feels very unSwifty. Comma juggling feels far more like a usability concern that should be solved with tooling than something that should undermine the language.

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

I've only ever used trailing commas in array literals, which I do like. I always feel bad for leaving them, though.

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

Read the proposal and comments.

6 Likes

Getting rid of all commas is briefly mentioned in the proposal, but I did not see a single mention while skimming through the discussion.
To me, this is a much better fit for Swift - or would anyone want to have semicolons enforced like in C?

Yes, it is possible to have both options in the language, but I really do not like such an „everything but the kitchen sink“ approach, so I see this as a clear either-or choice.

1 Like

I personally never want to see commas be something that can be elided, and I don't think it could be done feasibly without creating significant issues both to parser complexity and human readability. It's very easy to see where this could cause problems:

someFunction(
  foo
  .bar
)

Is that a call with a single argument foo.bar or two arguments foo and .bar? You want parsing to be completely decidable before type checking (waves to C++ :wave:), so you can't say "well I'll just look at the function signature to figure out which one it is".

So the compiler would have to just make a choice of how to parse that and the likely choice is to parse it as foo.bar, so this would create a bunch of places where users are forced to introduce disambiguating commas at locations that feel almost random. We already have that particular leading-dot problem with result builders due to semicolon elision, but doesn't often impact users in practice because result builder APIs are usually designed to avoid implicit member references. It would be much harder to avoid the need to use disambiguating commas.

It would also make code generators/macros trickier to get right. Imagine that you're generating the AST for the call above, and you want to use two parameters. You generate the following tree:

FunctionCallExpr
  "someFunction"
  LabeledExprList
    LabeledExpr
      "foo"
    LabeledExpr
      MemberAccessExpr
        "."
        "bar"

The macro system renders that AST to source text for the compiler, which then parses it back as:

FunctionCallExpr
  "someFunction"
  LabeledExprList
    LabeledExpr
      MemberAccessExpr
        "foo"
        "."
        "bar"

Oops!


I was against SE-0084 when it was originally proposed, but I've changed my views since then. My rationale for this change pretty much echoes what was already said in @beccadax's reply above. But I also hope that if we accept this, it will be a clear stance on how commas are used in Swift that will be the final nail in the coffin for the "elide commas" option.

17 Likes

This is potentially ambiguous, though. The implication is that if you then delete the //, the IDE should restore the comma after 2.

But what if the comment was actually originally part of the second parameter, wrapped to a new line? For example if it was "-3", and if some() has an optional third parameter, the IDE doesn't know if I intended to call some(1, 2 - 3) or some(1, 2, -3).

13 Likes

I am indifferent about this proposal but have a strong opinion about readability discussions in general.

I've used Swift for a decade without trailing commas (and longer still in Objective-C) and never found that to be a noticeable problem.

It seems the proposal has a minor to medium ergonomic benefit and a minor to medium readability drawback.

If the proposal is accepted, I think I will mainly end up using the feature accidentally because the compiler will let me do things it previously didn't. I also don't think I personally will find code using this to be much less readable.

But, what I feel more strongly about is that when people raise readability issues for an opt-in syntax like the one proposed, it seems their concerns are typically dismissed by saying:

  • It's opt-in, you personally don't have to use it
  • You can use a linter to remove uses of the opt-in syntax

This makes assumptions that the only Swift code you will ever read is:

  • Code you have written yourself
  • Code where you have control of the style / linter

I believe these assumptions are fallacies that gloss over readability concerns.

If you are a Swift developer, there are no opt-in parts of the language.

If it is allowed in Swift, at some point you are going to see it and need to understand it. It may be:

  • An open source package you import and need to debug or understand
  • An existing code base when you join a new organization
  • Sample code or an answer to a question here in the forums
  • Any other place you need to read and understand Swift code that you don't control

I hope feedback of reduced readability in this review receive the full consideration deserved and not be dismissed or diminished by fallacious opt-in assumptions.

38 Likes
  • What is your evaluation of the proposal?

+1 This is a natural extension of the trailing comma situations already allowed. It has no negative impact to people who don’t like the style and allows lists to be quickly reordered and edited when written with line breaks after the commas.

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

Given its low impact I think it solves a big enough problem.

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

The benefit is already clear from collection literals and it feels correct to continue it.

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

Again, going off collection literals these are natural extensions. Other languages that allow it work the way I hope this will in swift and work well.

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

I am familiar with the support of collection literals and read through the examples. I think that the unsupported situations are cases I would already expect it not to work.

6 Likes
  • What is your evaluation of the proposal?

-1

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

It's a quality of life improvement in which, IMO, the benefits do not outweigh the cost. For me, the cost is unclarity at the point of use. I can see a benefit during development. But I think it is very minor and sacrifices clarity of code intent.

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

IMO, no. However there is already precedent already for trailing comma in collections, so I guess it fits in with the direction of Swift in that regard.

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

Have not used.

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

I read the proposal, read the comments in the evolution thread.

1 Like
        let headersFromArrayLiteral: HTTPHeaders = [HTTPHeader(name: "b", value: ""),
                                                    HTTPHeader(name: "a", value: ""),
                                                    HTTPHeader(name: "c", value: "")]

↑This is from Alamofire, but i will accept:↓

let headersFromArrayLiteral: HTTPHeaders = [
    HTTPHeader(name: "b", value: "")
    HTTPHeader(name: "a", value: "")
    HTTPHeader(name: "c", value: "")
]

Totally, -1. If the trailing comma is going to be killed, I hope it's all gets killed, just like Swift killed the semicolon.

As was probably mentioned many times before, any pitches for making trailing commas optional should also include ergonomic disambiguation rules for leading dots, and also account for the fact that such rules only make the language more complicated.

Does this array contain a single element or two elements? If it's two elements, how would that maintain compatibility with existing code that currently expects it to be parsed as a single array element?

let headersFromArrayLiteral: HTTPHeaders = [
    HTTPHeader(name: "b", value: "")
    .a(value: "")
]

One could say that the compiler should be smart and infer whether .a is a static or non-static method on HTTPHeader, but that would make the parser intertwined with the type-checker (definitely not trivial, since Swift Syntax doesn't have access to the type checker), making parsing (and the language itself) much more complicated.

Also, what happens if HTTPHeader has both static and non-static methods called a(value:)? Right now it is easily and unambiguously parsed as a non-static method invocation.

Parsing rules that depend on surrounding context also make it harder for users to keep track of them at a given moment when writing or reading code.

17 Likes

I think this is a reasonable proposal. It can make certain tasks easier and it doesn't necessarily make the language more challenging to learn for beginners, it doesn't make the language more difficult to read and it doesn't affect the clarity of intent. Let people use what they want to use.

1 Like

I wonder whether some of the readability arguments would be ameliorated if the training comma was only allowed in multi-line variants and not the run in variety?

I don’t know enough about how whitespace is handled in the compiler to know if this is practical to implement, and my vague sense is possibly there is an explicit approach to ignore whitespace differences? If so that would obviously preclude this suggestion.

1 Like

It's certainly possible but that just feels like a job for swift-format or a linter, a rule similar to multiline_arguments

4 Likes

+1! :heart:

Was going to write a review myself but since it would basically be "whatever Max said", consider this a big +1 for the very same reasons as what Max gave.

Having to edit unrelated line (by adding/removing commas) to make a change compile is not only surprising but also annoying. Same for unrelated lines showing up in diffs.

8 Likes

I think this proposal would be a moderate improvement to Swift. Being able to comment function arguments in and out without fiddling around with commas would be a welcomed improvement for me. But having to fiddle around with commas isn't a difficult problem to overcome, and it's far from the worst thing about writing modern Swift.

I do share the same concerns as many other community members here, though, that this proposal is likely to encourage people to put the terminating right parenthesis of multi-line function calls on the next line rather than putting it next to the last argument. This is bad because:

  • It uses an entire line of code just for a single parenthesis. And the parenthesis isn't even useful for anyone reading the code! You can already see where a function call ends just by looking at the indentation of the code, so this style makes the entire line of code a waste of valuable screen space.

  • Having multiple different options for doing similar things contributes to fragmentation in the community. Fragmentation makes it harder to reuse code across codebases because you have to reformat the code to fit that codebase's style. Additionally, you need to remember the rules for each codebase you work on. By standardizing on minor formatting details, Swift code looks generally consistent and programmers don't have to weigh the pros and cons of each formatting style.

I also don't like the way this proposal encourages the developers of macros to use it. When macros were introduced, one of their main selling points was that macros are transformed into regular, readable Swift code — one of the first things demonstrated about macros in WWDC23's State of the Union was Xcode's "Expand Macro" feature. This proposal encourages macro developers to make their code less readable by adding extra commas where regular Swift code wouldn't. Plus, it doesn't really fix the underlying problem that creating lists in plugins and macros is a poor experience. You'd still have to special-case code in if/guard statements, for example, because they don't support zero-element condition lists. Rather than encouraging people to abuse trailing comma functionality in order to make writing macros easier, I think we should make writing macros easier by adding functionality to swift-syntax to generate normal-looking lists easier, among other things. (Currently, doing something as simple as properly parsing a float literal practically requires a third-party library!)

I'd like to see this proposal get accepted, but only if we can be reasonably sure that this won't further encourage people to adopt the "useless parenthesis" style of code. Personally, I think it's pretty risky. There are plenty of posts in this very thread with people saying that they plan to use trailing commas this way. And, as the proposal points out, plenty of official Swift projects use this style including the standard library, swift-format, DocC, and the default package templates. swift-syntax also uses this style pervasively, as well as the "mandatory self" code style that was rejected (for reasons that are still valid!) with SE-0009. If the Swift project can't follow these formatting rules consistently, then how can we expect individuals and other organizations to?

1 Like

I'd say:

  1. treat it the way it is currently treated – so this is always a single element array. It would be parsed as such even if the first line doesn't have .a() function, leading to a compilation error. I.e. never fallback to parse this as a two element array.

  2. If two lines are required – either prefix .a with a name (e.g. Self.a() or whatever), or insert a comma at the previous line in this particular case.

IMHO that way this should be unambiguous, understandable, backward compatible and quite cute.


Re the pitch itself –1 from me. I'd prefer having no commas in the multiline case similar to how we do it with semicolons.

Many people will put the end of a function call or signature on new lines for lots of clarity and arguably better indentation.

func foo(
    a: Int,
    b: Int,
    c: Int,
) -> Int {
    ...
}

foo(
    a: 1,
    b: 1,
    c: 1,
)

I personally do this with my own linter, it actually makes things a lot clearer (imo, since this is a subjective linting aspect). Additionally, braces on new lines instead of at the end are very familiar with a large precedent in other languages.

5 Likes

I think that putting the terminating parenthesis on the next line is OK for function declarations since you're probably going to have the return type and opening brace for the code block on that line anyway. My issue is with people using that style for function calls.

I don't really see how this allows for "lots of clarity" though. As I pointed out in my post, you can see where a function call ends without putting the terminating parenthesis on its own line by looking at indentation (as long as your code is properly indented). I also don't understand how this allows for "arguably better indentation", too. Can you elaborate?

It's also helpful to put the closing paren on the next line when it's a chained function call, to keep the indentation levels consistent (and this is what swift-format does):

f(
  one,
  two,
  three
).g(
  four,
  five,
  six)

It's easy to make broad pronouncements about when newlines do or don't make stylistic sense, but the reality is usually more complicated than that. I certainly think it's better to just say "it's totally fine to put a comma and a line break after six as well instead of trying to thread the needle like "you shouldn't put the paren on the next line, unless it's a function declaration since there will be a return type/body after it, and unless it has something else immediately following the call like an expression, and unless something else..."

11 Likes