SE-0257: Eliding commas from multiline expression lists

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

But those are pretty much the same example again. Is this valid Swift?

func foo(bar: Int) {...}
...
foo(
  bar
  : 1
)

Yes, the following is a valid Swift program:

func foo(bar: Int) { print(bar) }

foo(
  bar
  : 1
)

It compiles fine and prints 1.

We should probably stop taking up space in this thread with stuff that everyone can simply test and/or discuss elsewhere.

3 Likes

I have mixed feelings here, and I’m not keen on the specifics being proposed.

Commas are syntactically light-weight, and at the end of a line they are barely noticeable. This works both ways—on one hand, they are so minor that they are neither noisy nor distracting, so there’s no harm in keeping them. On the other hand, they don’t really add much visual information either, so it’s probably not a big loss if they go.

From a purely “clarity of reading” perspective, I don’t see much difference either way. Taking an example from the proposal, I think that aligning the colons in a function call provides a vastly greater benefit to readability than anything comma-related:

let config = TestConfig(
  delim         : "abc",
  sampleTime    : 42.0,
  numIters      : nil,
  numSamples    : nil,
  quantile      : nil,
  delta         : true,
  verbose       : false,
  logMemory     : true,
  afterRunSleep : nil
)

This way it is much easier to see at a glance what values are being passed in which order, and it is *also* easier to locate any particular argument label since there is more space around them. By contrast, the presence or absence of trailing commas has a frankly negligible impact on readability.

• • •

If we were to allow commas to be omitted from lists, I would want to place some restrictions on when it can be done:

1. Consistency within a list
If any item in the list (other than the last) omits a trailing comma, then all items must omit them. Mixed lists would be a mess to untangle when reading code.

2. Single-line items only
If any item in the list spans more than one line, then the list must use trailing commas. We should not have to contend with multiline entries in a new-line delimited list. This solves the ambiguity problem by requiring commas in those cases.

• • •

I don’t have a strong opinion overall, except that a change of this nature should need to be found not only beneficial and desirable, but also non-harmful. We certainly don’t want to get in a situation where we accept something like this, only to later find out there were unforeseen problems that make us want to roll it back.

We’ve been through that once before with access control, and it would not be good to repeat such an exercise.

1 Like

This would be…well, "impossible" is perhaps too strong a word, so let's just say "not straightforward". The problem is that you want an argument list to be parsed differently depending on the declaration it calls, but the declaration being called is not unambiguously known until typechecking, which can't be performed until parsing is long over. If it could be done, it would take a terrifying hack.

Sorry, I’m not in front of my computer right now and can’t check.

The reason I’m asking is that I’m trying to figure out if the original example that @jawbroken posted is an oversight in the grammar spec (and should be corrected) or if such very subtle differences in code are intended to be allowed. If they are, it seems like there could be some unfortunate interactions between that grammar and elided commas WRT variadic argument lists.

Parsing could be done with the assumption that commas are optional and but then compiler issues an error during type-checking if they’re not allowed. Not sure if that’s considered a hack but as I said it’s a crazy idea :stuck_out_tongue:

Probably need to discuss this on another tread

1 Like

Would it still be terrifying if the annotation was made for a body of code? Like

func foo() {
  // calls in here need all the commas
  @commaAreOptional {
    // calls in here don’t need as many commas
  }
}

(I’m not so much asking if this is a good idea, just whether it’d still be hacky to implement)

I assume that you're hoping to get some of the "exceptional" cases, like leading dot syntax, interpreted differently when the declaration is @commaAreOptional. If you aren't and you're just suggesting we should diagnose an error when you use this feature with a declaration that hasn't been blessed, I don't think the inconsistency is buying you anything.

True but it’ll at least avoid the inconsistent looking code that we can now end up with:

let foo = [
1,
2
3
]

Maybe a linter is a better tool for catching that but it’s a little annoying that this will be now possible to do unintentionally.

Is it possible to correctly parse when the type is declared in another module?

There's no similar warning or error for semicolon consistency

let a = 1;
let b = 2
let c = 3

so I don't see the need for this. Anything other than potentially confusing cases (which might deserve a warning) should be handled by linting/formatting. People who care will naturally be consistent, and people who don't care won't be bothered by inconsistency.

Edit: i.e. In the following, I strongly believe that 1 shouldn't be addressed, but 2 should be discussed

That's right. This isn't a vote, and we'd prefer more detailed feedback — but if it's all you've got time and energy for, then "+1" or "-1" or "I agree with Xiaodi" is a lot more meaningful to us than a :heart:. As a review manager, I'm not going to crawl over the thread collating likes.

4 Likes

Mildly +1, assuming tooling doesn’t suffer. I’ve always wanted Swift to allow trailing commas and use them in literals, but this seems like a viable alternative.