Pitch: Eliding commas from multiline expression lists

I realize this won't be taken very seriously, but COBOL requires none of commas, semicolons or even line-delimiters. The following 3 sets of statements all of the same meanings.

compute a = b + length(c) 
call s using a b c returning r 
display r

compute a = b 
          + length(c)
call s using a 
             b 
             c
       returning r 
display r

compute a = b + length(c) call s using a b c returning r display r

While I would not recommend this last one, it works just the same.

2 Likes

This is like when you notice that you can see your own nose, and then you can't not see your nose anymore. Now that we're talking about it, those commas look terrible.

(Apologies to everyone reading this who now has to stare at their nose for a while.)

16 Likes

While I appreciate the sentiment for this proposal, the exceptions in the “When will you still use commas?” are reason enough for me to go against it.

For example, while I agree that a multiline list can look nice without commas:

let list: [MyEnum] = [
    foo
    bar
    baz
    bing
]

I’m annoyed that the ambiguity forces me to add a single comma as soon as I want to add a literal enum case:

let list: [MyEnum] = [
    foo
    bar,
    .default
    bing
]

Even worse, it can make a small indentation error result in code that is difficult to parse for humans. It’s easy to forget that commas are sometimes necessary to avoid ambiguity and be caught off thinking the following list is of 4 elements when in fact it’s of 3 elements:

let list: [MyEnum] = [
    foo
    bar
    .barProperty
    bing
]
6 Likes

Ideally, the compiler would warn about that possible mistake and suggest adding a comma. Additionally, a style formatter should probably go further and add commas for all elements in a list that has one or more commas, to make it visually consistent.

This is the same comma requirement we have today, though I acknowledge that argument isn't entirely fair--making comma elision the norm suddenly makes adding commas feel like a standout burden. On the other hand, there's precedent for burdening the user to resolve ambiguities, such as backticking a reserved word.

The compiler won’t warm about it if bar.barProperty is a valid MyEnum. That’s the point: valid code can be harder to understand if not use indented properly.

-1 here. :-1:

Comma is not a noise. It's a necessary separator which would make our code more readable. If you think it's a noise then you should also elide all comma in other cases as well. That would be much more consistent with the motivation.

The exception cases are against the motivation of the pitch itself. The simpler solution would be just allow parameter list to behave like item list so the code below would be accepted by Swift compiler.

print(
    "red",
    "green",
    "blue",
    "cerulean",
)

Everybody happy! :blush:

2 Likes

If anything the statement, as I understood it, by @Douglas_Gregor that removing semi colons blocked the language from evolving properly (in his answer to @clattner) makes me wish for those back instead of removing the commas I also find this to be further sugar that makes the language less readable than with.

So yeah, a -1 on this here.

This is the thing, like with ternary operators or default formatting, that makes me a bit concerned about this is that I would expect this when things such as co-routines / async-await / “actors” model are done dusted, generic existential done, argument labels in closures back, etc... can more things be looked at in parallel? Yes, sure... but there is opportunity cost to thinking about any of them.

Where is this statement?

Apologies, it was not Dave A., but Douglas G. whose words I was referencing.

What I'm saying is that if comma elision leads to a potentially misleading situation, the compiler should warn about it and suggest resolutions: merging the elements into bar.barProperty, separating them with a comma, or specifying Bar.barProperty if such a type property exists.

The code is practically just as deceptive with commas and should be warned about today:

let list: [MyEnum] = [
    foo,
    bar
    .barProperty,
    bing
]
1 Like

The issue is not the deceptiveness. There's no intent to mislead anyone. The issue is that, without knowledge of what bar is, it's not possible for a human to reason about that list.

2 Likes

I will argue that that is and should be true in general cases that are not trivial. You need to have more than a cursory knowledge of the types at play to reason about non-trivial code.

1 Like

I feel there is a qualitative difference between knowing the types of the values and knowing how many values there are supposed to be.

2 Likes

I agree that that code presents a particular challenge to the reader. My completely personal and subjective take is that that is a really flimsy and bad example, though. There isn't a great reason to split that property from the instance there. I understand that this is a constructed example meant to illustrate a point and I take that point, I just think that it is difficult to construct a reasonable situation where you will bump up against the illustrated problem while writing code that would be acceptably clear without this change.

2 Likes

I was all for the aesthetic arguments of this pitch, but chris lattner's first impression made nervous. Doug's comment has assuaged my fears, so now I'm all in favor! Though if there are some expected future feature that this pitch would "break" I would want to know.

My experience of using YAML instead of JSON and of learning to read Haskell has prepared me to approve of this pitch, I think.

YAML lets you use both of these forms interchangeably, which is strange at first but quickly becomes natural:

Fruit: ["Apple", "Orange"]
Fruit:
    - Apple
    - Orange

Haskell, and the more relatable Bash, require commas almost nowhere, at least in function application.

map my_fn my_list

git add my_file another_file

Though I think the biggest win will be in writing collection literals. Consider also Ruby's array literals, which forms elements entirely based on white space. Here, the same array of strings:

fruit = %w(
  Apple
  Orange
)

Clearly this is stranger still, yet rubyists acclimate quickly because it's quite common.

I love that this pitch makes commas optional (not removing them entirely). I think it fits Swift's vibe of "annoying things are optional until it's ambiguous" e.g. type declarations, semicolons, generic constraints.

Here's a similar example from earlier in the thread that isn't unreasonable and is likely to confuse unless you have a strong understanding of how prefix and infix operators work in Swift.

@Douglas_Gregor made a great argument about the similarities between semicolon elision and comma elision, and everyone accepts that it's not technically ambiguous, but there are key differences in the possible warning behaviour that are being glossed over. How important you find good warnings in cases like these will affect how acceptable this pitch is to you.

2 Likes

I'm honestly in favour of this pitch. Currently, the separator in multiple expression lists consists of a new-line and a comma, but the visual context given to the reader is provided by the newline character. The comma is just noise since it doesn't provide as much visual context as the newline character.

If tools don't take a quality hit because of this change and it's not source breaking, this could be an interesting proposal. As I see it, this change would also align the logic with the usage of a semi-colon to split multiple statements declared on the same line (I personally never write such code, but it's a feature regardless).

-1 from me on this as well.

Semicolons and commas serve different purposes and removing them has different effects on our code. In many of the examples given we see that removing commas is actually a change to the method signature rather than just a syntactical text editing user preference.

2 Likes

I've used this feature in other languages, and I'd love to see it in Swift. It would be a significant boon to the ergonomics of declarative-style APIs, of which there are some good examples in @Michael_Ilseman's post. I use these types of APIs quite a bit and find that juggling commas around as changes are made is a constant nuisance, and they add a lot of noise to non-trivial lists.

I have a few suggestions that might help alleviate some of the concerns raised in the thread.

First, each list should be all or nothing on commas. imo, using a mix of commas and no commas is more harmful than helpful, and in cases where you're concerned about ambiguity, you can start inserting commas and the compiler will force you to use them correctly as it always has. Users who don't want to use the feature could also avoid unintentionally introducing it when leaving off a comma by accident.

We could also raise warnings or errors for cases that we deem to be problematic, forcing the use of commas or less ambiguous formatting. We might enforce that member access cannot start on its own line in an uncomma'd list:

// Invalid
let list = [
    foo
    bar
    .baz(x)
]

// Valid
let list = [
    foo
    bar.baz(
        x
    )
]

Similarly, to address arithmetic ambiguities, we could say that infix operators cannot start on their own line.

// Invalid
let list = [
    1
    - 1	
    + 1
]

// Valid
let list = [
    1 -
    1 +
    1
]

This doesn't address the potential confusion around accidentally using the unary prefix versions of these operators. We could forbid starting a line with a unary prefix operator, but imo that's too great a limitation to be worth it.

// Still valid, producing a three-element list
let list = [
    1
    -1	
    +1
]

To be clear, these rules would only apply to uncomma'd lists. The syntax would continue to be valid wherever it's valid currently.

I feel that this strikes a nice balance between usability and potential misuse of the feature.

4 Likes

I agree with Jarod that, if we're going to add this feature, we should require consistency within any given list.

7 Likes