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

+1

Very much in favor of this change. I hate the inconsistency, and the friction it adds when I’m rearranging statements. Maybe give me an optional warning for trailing commas, but let me have them.

3 Likes

-1 one from me.

A trailing comma takes at least one byte of additional storage.

Some tiny amount of energy is consumed for displaying it, storing it, retrieving it, and transporting it around the globe.

Now imagine trillions of trailing commas. :slight_smile:

3 Likes

I am very much in favor of this change; it's long overdue.

I'm very pleasantly surprised it does not lead to any known source breaks -- this sort of makes it a no-brainer improvement to me.

I'd also very much love if commas worked like semicolons, and Swift allowed line breaks to stand in for them, as described in Alternatives Considered.

The code style that "puts the terminating right parenthesis on a line following the arguments to that call" has been widely adopted by community, Swift standard library codebase, swift-format, docc documentation and Xcode. Therefore, not encouraging or endorsing this code style doesn't hold true anymore.

Overly pedantic nit: the stdlib guidelines do have a hard requirement for a line break before closing delimiters within function signatures and other declarations. It does also allow putting the closing delimiter on a separate line for function calls (or array and similar literals), but this is not a requirement -- in fact, the usual practice is to put the closing delimiter on the last item.

From the stdlib guide:

For lists that have delimiter characters (( /) , [ /] , < /> , etc.), we prefer to put a line break both after the opening delimiter, and before the closing delimiter. However, within function bodies, it's okay to omit the line break before the closing delimiter.

// GOOD:
func foo<S: Sequence, T>(
  input: S,
  transform: (S.Element) -> throws T
) -> [S.Element] {     // Note: there *must* be a line break before the ')'
  ...
  someLongFunctionCall(
    on: S,
    startingAt: i,
    stride: 32)        // Note: the break before the closing paren is optional
}

The proposal's point still stands, of course.

7 Likes

Yes please! Any organization or developer that doesn’t approve can just not use it, or even enforce not using it with a formatted or linter.

5 Likes

I would also consider the way Python’s Black formatter handles trailing commas: where possible, it interprets the presence or absence of a trailing comma as the user’s desire to have the list on multiple or one line(s) respectively, max line width permitting. Deleting a trailing comma and pressing “format” will put a list entirely on one line if possible; adding a trailing comma will break the list into one line per element.

6 Likes

What is your evaluation of the proposal?
A big +1, for the positive effect it will have on adding, editing, reordering and commenting out multi-line function parameter lists—my now preferred way to format all but the most simple function definitions and calls.

Is the problem being addressed significant enough to warrant a change to Swift?
I am covered in paper cuts from the number of times I have been chastised by the compiler for trailing commas over the last week alone. My preference for all but the shortest function definitions now is to list each parameter on a separate line, because of the positive effect this has on both diffs and code readability when in narrower columns. In these cases, editing, reordering and adding parameters constantly results in the compiler complaining about the presence of a disallowed trailing comma on what has become the last line. It is a currently tedious change to make over and over again for no discernible benefit.

Does this proposal fit well with the feel and direction of Swift?
I think the proposal fits well with the feel of Swift. I think few if any people will utilise the feature in one-line cases, and a linter can ensure that developers who find that use hard to read will never see it. I think allowing the multi-line case is clearly superior, particularly for function parameter lists.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
I spent a year writing Dart code in a previous contact. After becoming accustomed to trailing commas in function parameter lists—which was allowed in Dart and encouraged by our team's linter—I found the return to Swift's lack of support for this feature jarring. Contrary to arguments that newcomers might find this capability confusing, I think it is reasonable to foresee a significant number of newcomers will be repeatedly surprised by the paper cuts when the compiler does not allow this stylistic preference to be chosen.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
I read the full proposal and all responses in the thread above.

In the course of reading the replies, what I noticed was that the people who are against the proposal are almost entirely against it because they feel a slight ick factor about code they do not want to see, whereas the people who are for the proposal have strongly positive reactions about the way it will remove something that is a significant real paper cut that repeatedly creates unnecessary work for them. The former should get a linter, while the latter should get this feature.

16 Likes

Mostly +1 here.
Yes, adds a bit confusion in edge cases, but as others already said many times language will be more consistent.

2 Likes

+1 for editing Swift PM files alone.

12 Likes

This change should be made, but only as part of a larger change. The proposal is incomplete.

Unlike the Eliding commas from multiline expression lists proposal, this proposal does not offer a single example where a comma at the end of a line should be required. The respectful solution is to offer developers the option to include one, or not.

1 Like

I don't feel strongly about it, but this change would simplify some macros that need to construct or transform argument lists.

9 Likes

Can you elaborate more on what you feel is incomplete about the proposal? I am not sure I understand what you mean by examples where a trailing comma is required—that is never the case with or without the proposed solution.

4 Likes

Strong +1

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

Definitely! Consistency, better editing, nicer diffs, less conflicts when merging.

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

It should have been like that from the beginning.

How much effort did you put into your review?

I read the proposal and through the reviews up to here.

6 Likes

My evaluation of this proposal is neutral. If it exists, I would use it; if not, I have no complaints.

However, I find this part of the motivation a bit curious:

The language has also seen the introduction of parameter packs, which enables APIs that are generic over variable numbers of type parameters

Even before the introduction of parameter packs, variadic arguments were supported as a language feature. While parameter packs expanded the applicability scope, I don’t think they significantly changed the syntactic complexity. Is this motivation truly sufficient to justify the proposal?

2 Likes

The point in the paragraph was to highlight how these new additions to the language expand usages for trailing comma.

1 Like

Huge +1 from me! So much better for formatting and changing lists as well as for diffing.

People who don‘t like it can choose to not use it. Swift has a lot of things that can be written multiple ways, e.g. in didSet you can either use the default oldValue or you can redefine it. You can use && or comma for boolean-and.

I personally love trailing commas on multiline arrays and would love to use this on multiline guards and multiline method calls.

5 Likes

It seems to me like most of the comments in favor of this change are due to how frustrating it is for a build to fail because a comma hasn't been correctly adjusted while commenting, reordering or moving code around.

This is more of a formatting issue and as such I would argue it should be addressed at the IDE level and not at the compiler level.

IMHO what's really missing here is a deeper integration between Xcode and plugins, VSCode already allows to run formatters and Xcode should too.

A simple example:
Before save:

function some() {}

some(
    1, 
    2, 
    // 3
);

After save:

function some() {}

some(
    1, 
    2 
    // 3
);

Overall, even though I am not against it (especially since it is an "opt-in" feature and as many already mentioned it can easily be avoided thanks to linters), I see this more ideally offered as a swift-format plugin, not as a compiler feature.

3 Likes

Two major points are unaddressed in that consideration:

  1. Not everyone uses an IDE like Xcode or VS Code, or their favorite IDE may not have appropriate plugins available.
  2. Running a formatter after saving won't help. The issue is not with removing a redundant comma, but with adding a missing one.

Plenty of editors provide hotkeys for moving a line up or down. Moving a line without a trailing comma up will generate an unparsable file. Consider this snippet (a real initializer in the SwiftPM codebase):

// valid parsable code
init(
  packageIdentity: PackageIdentity,
  underlying: Module,
  dependencies: [ResolvedModule.Dependency],
  defaultLocalization: String? = nil,
  supportedPlatforms: [SupportedPlatform],
  // 👇 no trailing comma
  platformVersionProvider: PlatformVersionProvider
) {

Now I want to give priority to platformVersionProvider, I press Option+Cmd+[ in Xcode or Option+Up in VS Code:

init(
  packageIdentity: PackageIdentity,
  underlying: Module,
  dependencies: [ResolvedModule.Dependency],
  defaultLocalization: String? = nil,
  // 👇 now this fails to parse
  platformVersionProvider: PlatformVersionProvider
  supportedPlatforms: [SupportedPlatform],
) {

No formatter can handle this, for swift-format specifically one would have to modify SwiftSyntax to accept clearly invalid code, which I don't think anyone considers as a viable option. And then the only remaining question: why don't we have this problem with arrays and dictionaries, but always have to manually fix up moved lines for functions and tuples?

8 Likes

But, that's their choice, isn't it? And part of the trade-offs between different editing programs.

A better way to phrase it might be that it raises the bar on editing programs, in terms of what feels comfortable for the language. Whether that's a good or a bad thing is less clear, though.

This still seems like an editing program limitation and something the program could easily solve. Once upon time they didn't correct indentation as lines were moved up or down, either, and now many do.

The only aspect of this which doesn't seem eminently solvable by editing programs is the diff aspect. But (a) that's not that big a deal anyway and (b) maybe that can be solved by diffing programs, too.

That's not to say it necessarily precludes a language change, just because there other possible solutions, but it is worth factoring in. Especially with recent acceleration of "AI"-enhanced code generation / mutation, which might make a lot of these details (syntax minutiae) less important.

1 Like

I was weakly in support of SE-0084 by the time of the review (though I’d had the opposite opinion in the pitch phase); this time, my support is much stronger.

To be clear, I think trailing commas are ugly and slightly harm readability; if this proposal is accepted, I hope the Swift project style guide (and, if I had my druthers, most style guides and linting tools) will recommend that trailing commas only be used with specific justification. But there are situations where trailing comma use is justified. Off the top of my head, I can think of three:

  1. Temporarily while debugging or refactoring code to facilitate quick changes; hopefully these uses will be cleaned up before they are committed or published.

  2. When it is anticipated that the entries in the list will grow or change throughout the entire lifetime of the code base; think, for instance, of a list of version numbers or platform names which will inevitably accumulate new items as the project evolves.

  3. When code is being automatically generated at compile time and will rarely or never be read by human engineers.

That third use case is the main thing that’s changed since SE-0084 was rejected—the ecosystem is now generating a lot more code than it used to. In 2016, SwiftSyntax didn’t even exist yet. Now, eight years later, our core tools, like SwiftPM and the compiler, include built-in features like build tool plugins and macros to generate code that is rarely read and never maintained by humans. Correctly interleaving commas is a much bigger pain when you’re mechanically generating code than it is when you’re writing it by hand; that difficulty serves no technical purpose and rarely matters for readability since we’re talking about code that isn’t normally read. It makes sense to allow a more regular syntax here.

The fact that trailing commas are ugly doesn’t necessarily mean that the compiler should reject them. Their meaning is clear, they don't invite mistakes, and there are several situations we care about where they may be a good idea. I don’t think “it’s ugly” is enough to outweigh the reasons to permit them.

38 Likes

There are no examples where a trailing comma is required.
Every other comma in the examples, at the end of a line, is currently required.

The complete solution is to combine SE-0439 and SE-0257. They solve the same problem. But they're currently presented as "alternative solutions", and should not be. The solution should be proposed as, "allow commas at the end of lines (and after the last item regardless of whether that's the end of a line), but don't require them". E.g.

print(
    "red", "green", "blue"
    "cyan"
    "magenta"
    "yellow",
    "orange", "chartreuse", "spring green", "azure", "violet", "rose",)

If SE-0439 goes through as-is, it will allow "eliding commas, but only for the last item in a list".

1 Like