SE-0257: Eliding commas from multiline expression lists

The review of SE-0257: Eliding commas from multiline expression lists begins now and runs through April 18, 2019.

The proposal is written by @nate_chandler and @anandabits.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager via email or direct message on the forums. If you send me email, please put "SE-0257" somewhere in the subject line.

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?

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

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

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

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

Thank you for contributing to Swift!

Ted Kremenek
Review Manager

7 Likes

-1 this is a drastic change.

I would however support a magical ExpressibleByExpressionBlockLiteral that maybe uses {}.

let myArray = {
    1
    2
    3
}

+1

I’ve always wanted trailing commas in expressions to be allowed. I hadn’t considered allowing no commas instead. I’m a fan. I also want to allow trailing commas though, for the situations where using commas helps disambiguate expressions.

8 Likes
  • What is your evaluation of the proposal?

Mildly +1

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

I initially didn't think so, but after seeing @Michael_Ilseman's examples of places he's wanted them (especially in large dictionary literals), I started seeing the , as noise.

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

I think it does. There is a lot of code out there that e.g. builds lists of complex data structures, etc. Would be nice to remove the noise from something like an array of multi-line initializer calls.

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

Thinking about the amount of time I've spent installing YAML libraries for different languages to avoid writing JSON because of commas and quoting and whatnot, I think this helps us write clearer Swift in cases where we're building large static data.

I think the motivating example of

let values = [
  1
  2
  3
]

isn't the use case where I'd omit the commas. I'm more inclined to omit commas in something like a Package.swift file, where I feel the commas make the structure feel too much like Swift, and not enough like a data description language.

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

I've been following the thread and GitHub PR for a while, and I've looked over the implementation.

1 Like

Would you mind adding a reason for the -1? AFAIK the goal of these proposal reviews is not a vote on the feature (the final decision is made by the core swift team, not by whatever gets the most votes), but to offer reasons why you think this is a good or bad idea for the core swift team to consider. If your reason was already mentioned, just poke the :heart: button under it, no need to make a whole new post.

3 Likes

I'm conflicted but lean on the side of a mild -1.

This is where the proposal is the weakest. I'm neutral on the "look and feel" changes. I think there are legitimate scenario where this change can be considered an improvement. The trade-off is we are adding a second way to do a thing that is well understood and easy to explain (with plenty of prior art, etc).

Maybe? In spirit it feels similar to eliding self., which comes with its own controversy.

I've seen code written in languages that does this type of eliding. I get how it looks neat. But I don't have substantial experience with these to add anything super insightful.

Cursory reading of the pitch thread and the proposal.

Is there a downloadable toolchain for this?

I’d be really curious to try it out, and see if my imagination of how it should work matches how it actually works in practice. Hard to evaluate the proposal without that. (Apologies if I’m missing something obvious.)

2 Likes

-1. I disagree and don't think it's valuable.

No, not at all. Even if it will be approved, please don't make it mandatory. Treat it like the way Swift treats ; (semicolon).

No, not at all. It's just an unnecessary and unimportant syntactic sugar with no significant benefit.

I don't use other languages that have this feature. Even if I do, I don't want to use it. Comma is not a noise, it's a necessary separator to make a list more readable.

Saying , a noise is like saying parenthesis is a noise. Someday someone would propose to eliminate parenthesis as well because s/he thinks it's a noise.

Why would anyone need to write this:

print("hello world!")

instead of this:

print "hello world!"

or this:

print "hello world" separator: ","

Do you think the parenthesis is really necessary? I think it's just a noise, the double qoute is enough. Hey, why don't we remove the colon too? Is it really necessary? Etc.

Remember Python 2 vs Python 3?

A quick reading. I disagree with the proposal because the idea and motivation aren't consistent. If it really need to eliminate comma, do it consistently and avoid ambiguous cases. The current proposal doesn't reflect that.

12 Likes

A strong -1

I’ve tried to convince myself of the aesthetic side, I’ve tried to convince myself of the practical side, I’ve even tried to go through and update a large framework of mine to see what this would allow. I’m not sold on any point. This change seems backwards to me: Swift already has some weird places where whitespace behaves in what some would call a “nonstandard” way (returns, member lookups, trailing closures) - contrary to our brace-based syntax which would seem to suggest we value explicit delimiters. This change pushes us even more in the direction of having a mixed approach. It special-cases yet another part of the grammar so we can save O(n) commas which naturally increases the semantic density of a line - by fiat. If delimiters were distracting in some way, the argument for eliminating them is clear, but the proposal itself mentions the strategy (I hope) we all use when reading code: we just skip them. (Maybe this is the budding curmudgeon in me that has gone blind to most delimiters in C-likes).

I notice, as well, the proposal makes sure to highlight as much verbosity as it can by using long argument lists to make its case. The most distracting thing for me in each example was that the functions were spelled with their entire argument list. After that, it was why there weren’t sensible defaults in place to control the growth in verbosity for the 90% case. The commas were the last thing on my mind.

Interestingly, the proposal actually does point out an inconsistency, albeit in the opposite direction: We should consider allowing trailing commas. The dismissal for readability reasons alone does not outweigh the benefits in tooling (code generation is slightly easier), and the idea that it could set us up for a future where single-element tuples are spelled (T,).

Consistency is one of the most valuable things any system can have. It allows the reader to extend their understanding of disparate parts of a large development based on prior experience alone. It allows a writer to instantly grok the style in which they should express their ideas. Having yet another style, another option for expression, another possible inconsistency, means expending more mental energy when moving across (large) codebases, where presumably this matters most going by the proposal’s very own examples.

32 Likes

I'm enthusiastically in favor of this proposal. This would be a surprisingly radical change to the aesthetics of the language -- but I believe the change would be for the better.

Before Codable was a thing, I used to have a fun side project for Swift serialization. One of the file formats of that project was text-based, with a syntax inspired by Swift's literals and initializers -- think JSON but with Swift syntax and lightweight types. For that project, I decided to implement comma elision (exactly as proposed), and the result felt incredibly right.

Swift has thoroughly spoilt me with its semicolon elision -- I can't work in C++ anymore without constantly getting annoyed at the darned things. After a mere few hours of working in my toy language, I started feeling the exact same way about commas, and they've annoyed me ever since.

My toy language was strictly limited to data construction primitives, with no enums and no expression syntax, so no ambiguities could ever arise from omitting commas. This is not the case with Swift itself, but I'm satisfied with the solution given -- the parsing rules given are reasonably straightforward and they seem implementable by automated formatter tools.

If the proposal is accepted, the comma elision parsing rules will have some API naming consequences -- we should add labels to any parameters in the second and subsequent position that we expect will often begin with a dot. Luckily, I believe we already tend to do this.


On the other hand, it has to be pointed out that Array literals containing enum-like values (enums, OptionSets, etc.) will unfortunately often require commas even with this proposal.

I believe this is why the EDSL example used weird free-standing functions to specify table column types. Most Swift programmers would've used enums there, since that simplifies entry through code completion. Unfortunately, enums wouldn't work nearly as well:

let table = Table(
    name: "Employees"
    columns:
        .guid("record_id", isPrimaryKey: true, nullable: false),
        .guid("manager_id", isPrimaryKey: false, nullable: true),
        .string("name", length: 1024, nullable: false),
        .int64("employee_id", nullable: false),
        .date("start_date", nullable: false)
)

Note how we're still required to add four (but not five!) commas. This is unfortunate, but it's not too difficult to work around, as long as we're designing the DSL from scratch:

let table = Table(
    name: "Employees"
    columns:
        Column(.guid, "record_id", isPrimaryKey: true, nullable: false)
        Column(.guid, "manager_id", isPrimaryKey: false, nullable: true)
        Column(.string, "name", length: 1024, nullable: false)
        Column(.int64, "employee_id", nullable: false)
        Column(.date, "start_date", nullable: false)
)

Perhaps this would've made a more convincing example. I expect code completion would make this nearly as easy to enter as the enum variant above.

Unfortunately, we already have at least one DSL in widespread use that wasn't designed with comma elision in mind. SPM's Package.swift files tend to be full of arrays of enum cases, and the proposal has limited effect on these:

// Adapted from https://github.com/apple/swift-protobuf/blob/master/Package.swift
import PackageDescription

let package = Package(
  name: "SwiftProtobuf"
  products: [
    .executable(name: "protoc-gen-swift", targets: ["protoc-gen-swift"]),
    .library(name: "SwiftProtobuf", targets: ["SwiftProtobuf"]),
    .library(name: "SwiftProtobufPluginLibrary", targets: ["SwiftProtobufPluginLibrary"]),
  ]
  targets: [
    .target(name: "SwiftProtobuf"),
    .target(name: "SwiftProtobufPluginLibrary"
            dependencies: ["SwiftProtobuf"]),
    .target(name: "protoc-gen-swift"
            dependencies: ["SwiftProtobufPluginLibrary", "SwiftProtobuf"]),
    .target(name: "Conformance"
            dependencies: ["SwiftProtobuf"]),
    .testTarget(name: "SwiftProtobufTests"
                dependencies: ["SwiftProtobuf"]),
    .testTarget(name: "SwiftProtobufPluginLibraryTests"
                dependencies: ["SwiftProtobufPluginLibrary"]),
  ]
  swiftLanguageVersions: [.v3, .v4, .v4_2, .version("5")]
)

Is it time for another Package.swift redesign?

let package = Package(
  name: "SwiftProtobuf"
  products: [
    Executable(name: "protoc-gen-swift", targets: ["protoc-gen-swift"])
    Library(name: "SwiftProtobuf", targets: ["SwiftProtobuf"])
    Library(name: "SwiftProtobufPluginLibrary", targets: ["SwiftProtobufPluginLibrary"])
  ]
  targets: [
    Target(name: "SwiftProtobuf")
    Target(name: "SwiftProtobufPluginLibrary"
           dependencies: ["SwiftProtobuf"])
    Target(name: "protoc-gen-swift"
           dependencies: ["SwiftProtobufPluginLibrary", "SwiftProtobuf"])
    Target(name: "Conformance"
           dependencies: ["SwiftProtobuf"])
    TestTarget(name: "SwiftProtobufTests"
               dependencies: ["SwiftProtobuf"])
    TestTarget(name: "SwiftProtobufPluginLibraryTests"
               dependencies: ["SwiftProtobufPluginLibrary"])
  ]
  swiftLanguageVersions: [Version3, Version4, Version4_2, Version("5")]
)

Would code completion be smart enough to make this as easy to enter as the original enum-based variant above?

6 Likes
  • What is your evaluation of the proposal?

+1, uncertain.
+1 because I've been annoyed by trailing commas long before this proposal, and reading the proposal makes me want to have it implemented right away.
Uncertain because I guess a meaningful evaluation isn't possible without actually trying this feature out in practice. But investing time and effort to clone and build the entire Swift compiler (with the implementation of this feature) is too big a thing for me. It would be nice if there were prebuilt toolchains with the implementation available (are there?), I don't know how many people will actually try out the implementation otherwise ... It could mean that most reviewers might be "blindly" evaluating this feature without actually having tried it out. Why not add a "• To what extent did you try out the implementation of this proposal? Not at all, a quick try, extensively." to the review form?

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

Yes and maybe no.
Yes because trailing commas are as annoying as eg trailing semicolons or explicit self..
No iff the change result in frequent ambiguities or other problems when writing code or increased complexity and/or bugs in the compiler etc.

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

Yes.

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

I've read the proposal and followed the pitch thread.

1 Like

-1

I’ve long wished that you could add a trailing comma to expression lists, for the exact reasons stated, but this just feels wrong. It’s not necessarily the end of the world if this passes (I can always use a formatted to enforce commas be added) but I worry that this will cause performance regressions and cryptic errors in “edge” cases.

3 Likes

We are not proposing that this be mandatory. It is a syntax that will best be used along with good judgement and should certainly not be used everywhere. As has been discussed abundantly, manly of the best use cases for this syntax will be in the context of EDSLs that either happens to work well with it or perhaps have even been intentionally designed to take advantage of it.

2 Likes

I am slightly negative on this proposal. I like when syntax is optional, because it gives the developer discretion as to when to clarify a piece of code with more verbosity. However, I feel that the number of edge cases where comma elision will cause problems will be more numerous than expected.

I feel that lists with a mix of commas and elided commas will be ugly, and more prone to bugs when edited. I do not think the experience of editing code will be improved if users who prefer to elide commas have to (re)introduce them when they add an otherwise ambiguous or semantics-changing entry to a list.

I don't feel that it will be a good experience to look at others' code and have to wonder if an elided comma is a bug or intended.

6 Likes

I suppose I'll have to repost this from the pitch thread because it doesn't seem to have been mentioned or addressed at all in the proposal under review (apologies if I somehow missed it).

I have come around to supporting the proposal, but I think this tradeoff should have been mentioned. There might be the opportunity for warnings here when operators have both prefix/postfix and infix forms, if deemed necessary.

let solutions = [
  2*(1-sqrt(a))*sqrt(b+sqrt(b))
  -(1+sqrt(a))(sqrt(2*b)-sqrt(2))
] // two solutions

let solutions = [
  2*(1-sqrt(a))*sqrt(b+sqrt(b))
  - (1+sqrt(a))(sqrt(2*b)-sqrt(2))
] // one solution
6 Likes

Does Swift allow expression-ish things to span multiple lines without some form of demarcation anywhere else?

Crazy idea: what if we limit the use of comma elision to EDSLs since they seem to be the major motivation for this proposal? There could be some new attribute @commaAreOptional which controls this behavior.

The following is valid Swift, and results in two (unused) values:

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

+1, assuming there are no harsh impacts on tooling quality or syntactic directions for the language.

From the most recent pitch thread (which I couldn't find the link for in the proposal though it has links to previous pitches):

IIUC, the parsing behavior is the same for the exceptional cases and if you want listing behavior you add a comma, which current code has.

The proposal mentions that we could offer a fixit if member lookup fails in a context where multiple values are expected. I know this is a nitty detail, but @nate_chandler, do you know how hard it would be to add this? Are we expecting this to be part of the implementation or follow soon after? IIUC, diagnostics for the other direction (i.e. commas need to be removed) are not as relevant.

@Douglas_Gregor, is the proposed diagnostic the same as what you're thinking of, or are you talking about something different or more general here?

By "a lot closer", are you referring to the syntactic impacts of this feature or the use cases? Are you able to address some of the concerns regarding parsing performance and poor diagnostics?

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

I feel so. I've always wanted to elide these commas in lists of generics, labeled parameter lists, dictionary literals, etc.

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

I think this will make Swift a better language for DSLs, configurations, and other places where long declarations exist.

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

Haskell, which has far fewer commas and parenthesis, making it a nice host language for EDSLs.

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

In-depth study of use cases, but not of the implementation or grammar impact.

2 Likes

Sure:

let v: Int =
    1
    - 1
    + 1
    - 1
print(v) // prints 0

let v: Int =
    1
    -1
    + 1
    - 1 
print(v) // prints 1 (and issues a warning for the line above this line)
2 Likes