Pitch: Eliding commas from multiline expression lists

Eliding commas from multiline expression lists

Swift requires a semicolon ";" to separate statements unless those statements are separated by newlines, in which case the semicolon can be elided. Currently, Swift requires a comma "," to separate expressions even when those statements are separated by newlines. We should ease this restriction, allowing the comma between two expressions to be elided when they are separated by a newline.

Motivation

When writing a list of expressions, you insert commas to tell the compiler where one expression ends and the next begins. When you add a newline between the expressions, though, you provide that same information by way of the newline characters. When newlines are present, commas provide clarity neither to human readers nor to the compiler.

In these cases, at best, commas can be overlooked by human readers. At worst, they cause visual clutter and obscure the meaning you are trying to communicate in our code. Consider the following sample, taken from Alamofire:

let protectionSpace = URLProtectionSpace(
    host: host,
    port: url.port ?? 0,
    protocol: url.scheme,
    realm: host,
    authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)

The commas here are not communicating anything. The writer has to put them in, the compiler has to observe that they're there and move along, and the reader has to filter them out. They're noise for all parties involved. Compare that to

let protectionSpace = URLProtectionSpace(
    host: host
    port: url.port ?? 0
    protocol: url.scheme
    realm: host
    authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)

The difference is small, but significant:
(1) There are no spurious characters between the parentheses. You are presented with a list of the names of the ingredients being used to construct the URLProtectionSpace on the left hand side of the colons and on the right hand side you see the values which are serving as those ingredients.
(2) The lines are symmetric. the last line, lacking a comma, looks no different from the others, because they all lack a comma.
(3) Each line stands on its own. Because they appear in single line argument lists, a comma at the end of a line has the effect of drawing your eye down to the next line. Without the commas, you have a moment to breathe at the end of the line, maybe to glance back at the argument label before moving on to the next line.

Let's take a look at a couple more examples.

To begin with, let's look at constructing another type which takes many arguments. Here it is with commas

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

and without

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

Once again, the result is cleaner. All the characters that you see are relevant and meaningful. Each line you see is like the others. You're not drawn from one line to the next by the comma, you're free to scan through the items at your leisure.

These same improvements are visible in expression lists besides the arguments to an initializer. Consider the following function calls, first with commas

StringTests.test("AssociatedTypes-UTF8View") {
  typealias View = String.UTF8View
  expectCollectionAssociatedTypes(
    collectionType: View.self,
    iteratorType: View.Iterator.self,
    subSequenceType: Substring.UTF8View.self,
    indexType: View.Index.self,
    indicesType: DefaultIndices<View>.self)
}

StringTests.test("AssociatedTypes-UTF16View") {
  typealias View = String.UTF16View
  expectCollectionAssociatedTypes(
    collectionType: View.self,
    iteratorType: View.Iterator.self,
    subSequenceType: Substring.UTF16View.self,
    indexType: View.Index.self,
    indicesType: View.Indices.self)
}

and then without:

StringTests.test("AssociatedTypes-UTF8View") {
  typealias View = String.UTF8View
  expectCollectionAssociatedTypes(
    collectionType: View.self
    iteratorType: View.Iterator.self
    subSequenceType: Substring.UTF8View.self
    indexType: View.Index.self
    indicesType: DefaultIndices<View>.self)
}

StringTests.test("AssociatedTypes-UTF16View") {
  typealias View = String.UTF16View
  expectCollectionAssociatedTypes(
    collectionType: View.self
    iteratorType: View.Iterator.self
    subSequenceType: Substring.UTF16View.self
    indexType: View.Index.self
    indicesType: View.Indices.self)
}

The difference is subtle but striking.

Editing difficulties

Another, more minor point is that commas in these positions cause problems when editing code. In Swift today, you can easily add or remove any item--even the last--from a collection literal by commenting it in or out:

let colors = [
    "red",
    "green",
    "blue",
//    "cerulean"
]

Unfortunately that convenience is not available fully in the other expression lists. For example, in a multiline function call, it is a breeze to comment out any argument

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

except the last; commenting it out raises an error:

print(
    "red",
    "green",
    "blue", // error: unexpected ',' separator
//    "cerulean"
)

The reason for these inconsistent behaviors is that trailing commas are permitted in collection literals but not in any other expression list.

One solution would be to allow trailing commas in all expression lists. That change, however, only addresses part of the problem. The visual noise that the commas cause not only remains but is magnified: to get this convenience, we would be incentivized to write our code with trailing commas in all multiline expression lists.

Instead, we should allow commas to be elided from multiline expression lists entirely. Without commas, the original function call would instead look like

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

with its arguments untarnished by commas. We would be free to comment out any line of it, including the last

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

because what remains is again a multiline expression list with commas elided.

Proposed solution

Rather than allowing comma elision in just some expression lists in an ad hoc fashion, this document proposes allowing commas to be elided uniformly in all multiline expression lists.

When will you still use commas?

When parsing an expression, the compiler keeps going until it can't any longer, following the maximal munch principle. Sometimes, though, you want one expression to end and the next to begin before the parser would otherwise stop. In those situations, you will use a comma to communicate that intent.

There are two scenarios where you will use commas to clarify that one expression is ending and the next is beginning:

Implicit members

When parsing a multiline expression list featuring a member expression which appears after a newline

foo(
    bar
    .baz
)

the member expression will be interpreted as modifying the expression that preceded it

foo(bar.baz)

rather than as a separate expression

foo(bar, .baz)

If you actually want .baz to be as an implicit member, an expression in its own right, you will add a comma:

foo(
    bar,
    .baz
)

Closures

In a similar vein, when parsing a multiline expression list featuring a closure which appears after a newline

foo(
    bar
    { print("baz") }
)

the closure will be interpreted as a trailing closure passed as an argument to the expression that preceded it.

foo(bar { print("baz") })

rather than as a separate expression

foo(bar, { print("baz") }

If you actually want the closure to stand on its own, to be its own expression, you will add a comma to separate it from the preceding expression.

foo(
    bar,
    { print("baz") }
)

These situations may sound familiar--they are exactly the same situations where we need to use semicolons to separate items in statement lists, even in the presence of newlines. In practice, you will need to use commas more often than semicolons because it is more often for these expressions to appear in expression lists than in statement lists.

That said, you will need to use them less often than it might at first seem.

Consider closures: Trailing closure syntax means that most of the time, closures appear after the end of the expression list. Typically, the above example would actually be written

foo(
    bar
)
{ 
    print("baz") 
}

What about implicit members? Consider a function call like this:

buyTreat(
    .savory
    .orange
)

This would be parsed as .savory.orange, which may not be what you want. Even to a human, reader, though, it's not clear what is meant. To make code obvious to readers, you often use argument labels (flavor: .orange) to provide a hint to readers of what the implicit member may be a member of:

buyTreat(
    .sweet
    flavor: .orange
)

If you would prefer to leave out an argument label, you could also provide the type Flavor.orange in order to provide a reader with that context:

buyTreat(
    .sweet
    Flavor.orange
)

If you don't want to use either of those approaches, only then will you end the prior expression with a comma.

Without this change, you are forced to use commas everywhere. In multiline expression lists, they are reduced to line noise and meaninglessness. A comma is a character to be ignored. With this change, if you omit commas whenever possible, when you write a comma, you will mean something: "without this, the previous expression would keep going; I want it to end here."

Detailed design

Swift will allow the comma separating two expressions in an expression list to be elided provided that there is a newline separating the expressions.

The grammatical productions from The Swift Programming Language will be modified as follows:

expression-list -> expression | expression , expression-list | expression \n expression-list 
function-call-argument-list -> function-call-argument | function-call-argument , function-call-argument-list | function-call-argument \n function-call-argument-list
tuple-element-list -> tuple-element | tuple-element , tuple-element-list | tuple-element \n tuple-element-list

With these few changes to the grammatical productions, comma elision will be accepted in the following positions:

  • array literals
[
    "red"
    "green"
]
  • dictionary literals
[
    "red" : 4
    "green" : 8
]
  • free function calls
print(
    "red"
    "green"
)
  • method calls
foo.print(
    "red"
    "green"
)
  • initializer calls
let instance = Widget(
    "red"
    "green"
)
  • subscript reads
foo[
    "red"
    "green"
]
  • subscript writes
foo[
    "red"
    "green"
] = "yellow"
  • super method calls
super.print(
    "red"
    "green"
)
  • super initializer calls
super.init(
    "red"
    "green"
)
  • super subscript reads
super[
    "red"
    "green"
]
  • super subscript writes
super[
    "red"
    "green"
] = "yellow"
  • enum instantiations
let e = E.foo(
    "red"
    "green"
)
  • tuple instantiations
let t = (
    "red"
    "green"
)
  • key-path subscripts
let path = \Gadget[
    0
    1
]

Source compatibility

This is not a source-breaking change. Extensive compatibility tests have been run against the change.

This document does not propose removing commas from the language. All code that is legal today will continue to be legal. This document proposes easing a restriction, making more code legal.

@blangmuir looked into SourceKit's under the change and determined everything just works without any other changes. Autocomplete continues to function as before.

Because statement separator (i.e. semicolon) elision has been in the language for so long, all the engineering problems for expression separator (i.e. comma) elision have already been solved.

Effect on ABI stability

N/A

Effect on API resilience

N/A

Alternatives considered

  • Allow trailing commas in expression lists.

While trailing commas in expression lists would provide the same improvements in the editing experience that comma elision does, they do not bring the same readability improvements to the language as comma elision.

  • Base interpretation of arguments off of semantic information.

The two exceptions listed above may seem less than ideal. It is tempting to ask whether we could decide the number of expressions in the expression list based on the context in which it appears. Swift does not currently do this sort of interpretation based on semantic information and doing so massively complicates the language.

14 Likes

To me, a change like this decreases readability due to the removal of the common list separator. This readability is ingrained at an early age for those of us who write a language which uses commas. It introduces the invisible newline as a separator, which seems like a net loss vs. a visible separator.

21 Likes

I was right with you until reaching the exceptions. Those would be a constant source of annoying bugs, and would force inconsistent formatting, possibly in the same list. I think users would be conditioned to ignore this feature, and I bet linters would default to disallowing it.

7 Likes

-1. I don't think it helps readability.

3 Likes

This would cause a lot of problems and not actually fix the issue you raise (multi-line function calls), because comma elision would be optional. I don't find the multi-line function call example that compelling, because function calls are not commonly written in a one-argument-per-line fashion anyway (unlike collection literals), and it's also not as common to be toggling off a single argument (limited to variadic and default parameters, or overloads simulating those).

An example of some confusion this might cause:

let a = [
   1
  -1
  +1
]

looks like it could be splitting a single expression across multiple lines, but is actually declaring an array with 3 elements. The very similar

let a: [Int] = [
  1
  - 1	
  + 1
]

currently produces an array with a single element. I don't think this is technically ambiguous, because unary operators can't be separated from their operands, but it is very subtle.

Note: Similar confusion already exists to some extent

let b = 1
+ 1 // b = 2

let b = 1 // b = 1
+1 // Warning: Result of operator '+' is unused

but at least you get a warning.

4 Likes

I am very much -1 on this proposal on several grounds:

  1. The entire premise of this proposal is that separators do not provide value to humans. This is not obviously true and I would like to see some evidence to defend this position.

  2. As you point out in your 'exceptions' section, the Swift grammar relies on juxtaposition and sequencing as part of the existing grammar. This is a source breaking change for at least some cases.

  3. Taking this significantly restricts future evolution of the language, by taking away juxtaposition from the expression grammar. This is extremely problematic for me given the early development of the Swift language. We should not be pouring sugar in as tasty mortar to fill the grammatical cracks in the Swift building - we need to be able to drop entirely new bricks into the language in the future, and that sugar will get in the way, and make evolution much more difficult.

  4. This is trivial sugar with almost no benefit. The only precedented discussions we have had along these lines have been about allowing an extra comma at the end of an argument list (to allow copy and paste) and those have been extremely controversial to say the least. I don't see why we'd go completely the opposite way and take away all separators.

  5. I suspect that this will significantly regress QoI on error diagnostics in common cases in the user/IDE experience.

23 Likes

My read on this proposal’s intent is that humans might not always need both a comma and newline as a separator. Did you read it differently?

let a = [1 2 3] // Not part of this proposal.
let b = [
    1
    2
    3
] // The subject of this proposal.
3 Likes

Another ambiguity:

frobincate(
    foo
    (6 * 9) // Function application or separate value?
)

Hi @Chris_Lattner3 – I'm surprised that you didn't respond to the point raised in the introduction. Fundamentally, why are commas between lines required but semicolons between lines are not required? Most if not all of the syntax/grammar problems are the same, right?

Hi @nate_chandler – While I'm sympathetic to your goal, I think that treating newlines as "whitespace" is fundamental to the syntax of Swift (and languages that Swift strives to look familiar to). The net result is that commas and sometimes semicolons are a necessary disambiguation token in the grammar.

Dave

2 Likes

To be clear, you propose preserving the existing behavior of these examples, right? This proposal is probably DOA otherwise.

(This might be good to mention in the "source compatibility" section, rather than leaving it empty.)

I thought so too, but Swift actually doesn't allow a newline immediately before the opening ( of a function call or the opening [ of a subscript. Your code sample emits an error telling you to add a comma after foo.

1 Like

I think you're going to need to establish much stronger motivation to have any hope of getting traction on this. The examples you listed actually look clearer to me with commas. You'll need to argue that this meets a high bar and is worth closing off some potential syntactic avenues for the future.

However, I am sympathetic to things that can enable better looking EDSLs in Swift. For example, would SPM manifest files like SwiftNIO's Package.swift improve at all? Do you argue that eliding commas would improve that?

What about potential command-line argument declaration packages, e.g. Commander? What about XCTest's allTests, like here?

You're going to want to build a strong case with as many examples and future directions as you can find.

8 Likes

Thanks to everyone for your feedback so far!

A few points of clarification:

(1) The pitched change would keep commas in the language, as semicolons are part of the language, to be used in cases where there is ambiguity for human readers and where are a particular interpretation needs to be communicated to the compiler.

For example, it would be reasonable to add commas when there is the possibility of ambiguity between prefix and infix operators:

combine(
    1,
    -1,
    +1
)

@DaveZ The situation with semicolons and commas seems even more similar--just like semicolons, commas are only sometimes necessary to provide clarity about where one element ends and the next begins.

(2) This is not a source-breaking change. Extensive source compatibility tests have been run against the changes and no breakages have been found. I went ahead and updated the source compatibility section to reflect this, thanks for the suggestion @beccadax.

The "exceptions" mentioned in the pitch are not cases where the interpretation changes from the current interpretation. Rather, they are cases where commas will remain necessary to indicate that one expression has ended and another is beginning.

For example, the following is currently legal code:

enum E {
    case one
    case two
}

func foo(_: E, _: E) {}

foo(
    .one,
    .two
)

With this change, it would remain legal code. Removing the comma, however, would change the meaning into something illegal

foo(
    .one
    .two
)

because .two would be parsed as a member access on .one. Note that this also is how that code is parsed today, without this change.

The closure case mentioned in the pitch is similar. The following is currently and would remain legal code:

func foo(_: Int, _ two: (Int) -> Int) {}

let bar = 42

foo(
    bar,
    { $0 }
)

Removing the comma would again change the meaning into something illegal

foo(
    bar
    { $0 }
)

because { $0 } would be parsed as an attempt to invoke bar with a trailing closure. As before, this is another case where the parsing without the comma is unchanged from today.

2 Likes

That's a great point. In fact, @anandabits had a very nice example of exactly this use case in the original thread where this was pitched about three years ago: SE-0084 spinoff: Newlines as item separators - #12 by anandabits . In addition to his excellent example, he makes the following point:

Thanks for this piece of feedback too:

I will survey the examples you listed and more and update the pitch with "before and after", illustrating the readability benefits of comma elision.

2 Likes

Does this pitch allow for commas to be elided in function/method parameter lists and generic parameter lists?

func parseVarRec<
    T: ContiguousBytes
    Header: FixedWidthInteger
>(
    buffer: T
    headerType: Header.Type
    output: (T) throws -> Void
) throws { [...] }
func bar(c: () ->()) { }

let a: [Any] = [
    bar,
    { }
]

print(a) // [(Function), (Function)]

let b: [Any] = [
    bar
    { }
]

print(b) // [()]
1 Like

It’s good that the compiler puts its foot down, but I’m ultimately more concerned with code that’s ambiguous to humans.

While these ambiguities exist in semicolon elision too, it feels like they’re more likely to arise in expression-lists than sequences of statements.

5 Likes

The same argument could have been made for semicolons, yet we "know" they don't provide value; or for commas after "case" declarations in an enum, yet of course we wouldn't have those (despite C having them). I look at something like this (grabbed at random)...

let best_pictures = [
    1973: "The Godfather", 
    1989: "Rain Man", 
    1995:"Forrest Gump"
]

And the commas add nothing except a weird inconsistency at the end with the missing comma . Or an initialization (taken from Alamofire):

let protectionSpace = URLProtectionSpace(
    host: host,
    port: url.port ?? 0,
    protocol: url.scheme,
    realm: host,
    authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)

The commas add nothing to clarity; they're just line noise, like the semicolons we eliminated before.

Your second statement is factually incorrect: allowing the elision of commas does not break any source code. A correct statement would be "if you remove all commas from all expression lists in existing Swift source code, it will break some of that source code."

The "exceptions" part of the proposal is listing those places, and that list should look very, very familiar to a compiler implementer: it's exactly the same set of places where you would need to write a semicolon to separate two statements. Comma elision and semicolon elision are the same feature, with different contexts.

Now, it is the case that expressions with a leading dot (.foo) are more common in expression lists than at statement scope, so the exceptional cases will come up more often with comma elision. However, given that we've seen very few problems in practice with the semicolon elision rules over the last 5+ years, I'm not concerned that we're actually causing problems here.

Juxtaposition is already restricted by semicolon elision for statements. It's not a viable mechanism for new bricks (which I hope are tasty ones), so this sugar isn't cutting off future evolution.

Regarding the "extra comma" discussion, some times you need to see what the cost of uniformity is (e.g., put commas after everything as a stylistic choice) to challenge your own assumptions. I thought we needed them, and had convinced myself that somehow semicolons were unnecessary yet commas were, and now that I've looked at it a lot closer... I was wrong. We just don't need those commas. They're noise and they get in the way.

I would have thought so, too! My main concern would be around the

  foo
 .bar

cases, for which we've never had good diagnostics in the semicolon-elision case because it hasn't been important enough. Now we'll have to do it, and all of Swift will benefit because we're applying the same rule more generally.

The other stuff in the IDE works shockingly well. We took the implementation of this and dropped it into SourceKit to see what would break when editing Swift sources where extraneous commas have been dropped, and... it worked. Because semicolon elision has always been a thing, the tools are prepared for it. We don't feel we need any SourceKit/code completion/etc. changes to make this work.

Semicolon elision paved the way for this change, already. We're in a good place to make it, now that we've realized we can.

Doug

20 Likes

Examples included in the pitch are nice and seemingly they look great without separator chars. However I fear lists expressed this way are easy to break with an unintended reformat made by a tired developer or an auto-formatter tool.
Swift becomes sensitive to white space changes like Python. And it is better to avoid.

1 Like

Neat, dictionary literals and named-parameter lists are good examples of some benefit to clarity.

Another would be listing of a struct's decl and the corresponding member-wise init. Example adapted from the benchmark suite:

struct TestConfig {
  let delim: String
  let sampleTime: Double
  let numIters: Int?
  let numSamples: Int?
  let quantile: Int?
  let delta: Bool
  let verbose: Bool
  let logMemory: Bool
  let afterRunSleep: UInt32?
}

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

I tend to stick in that trailing comma and/or forget to put any in the first place. The semi-colons are not needed to separate the decls, so it feels like this initialization shouldn't need it either. (This is an aesthetic and not a rational argument; I don't know the deep ramifications in the language if this were to be adopted).

If I look at benchmark declarations from the suite, like:

public let StringComparison: [BenchmarkInfo] = [
  BenchmarkInfo(
    name: "StringComparison_ascii",
    runFunction: run_StringComparison_ascii,
    tags: [.validation, .api, .String],
    setUpFunction: { blackHole(Workload_ascii) }
  ),
  BenchmarkInfo(
    name: "StringComparison_latin1",
    runFunction: run_StringComparison_latin1,
    tags: [.validation, .api, .String],
    setUpFunction: { blackHole(Workload_latin1) },
		legacyFactor: 2
  ),
  BenchmarkInfo(
    name: "StringComparison_fastPrenormal",
    runFunction: run_StringComparison_fastPrenormal,
    tags: [.validation, .api, .String],
    setUpFunction: { blackHole(Workload_fastPrenormal) },
		legacyFactor: 10
  ),
 ... a dozen more ....

I don't feel like the commas help and are more of an annoyance and distraction. These declarations are almost functioning like a mini-DSL.

@nate_chandler, I'd suggest looking at this suite for some more potential benefits for comma elision.

edit: More examples, this time from StdlibUnittest:

It's not everyday that we're directly calling into the collection validation framework, but it would be nice if it felt more like a DSL:

StringTests.test("AssociatedTypes-UTF8View") {
  typealias View = String.UTF8View
  expectCollectionAssociatedTypes(
    collectionType: View.self,
    iteratorType: View.Iterator.self,
    subSequenceType: Substring.UTF8View.self,
    indexType: View.Index.self,
    indicesType: DefaultIndices<View>.self)
}

StringTests.test("AssociatedTypes-UTF16View") {
  typealias View = String.UTF16View
  expectCollectionAssociatedTypes(
    collectionType: View.self,
    iteratorType: View.Iterator.self,
    subSequenceType: Substring.UTF16View.self,
    indexType: View.Index.self,
    indicesType: View.Indices.self)
}
...

And for declaring new tests to loop over and run

let replaceSubrangeTests = [
  ReplaceSubrangeTest(
    original: "",
    newElements: "",
    rangeSelection: .emptyRange,
    expected: ""
  ),
  ReplaceSubrangeTest(
    original: "",
    newElements: "meela",
    rangeSelection: .emptyRange,
    expected: "meela"
  ),
  ReplaceSubrangeTest(
    original: "eela",
    newElements: "m",
    rangeSelection: .leftEdge,
    expected: "meela",
    closedExpected: "mela"
  ),
  ReplaceSubrangeTest(
    original: "meel",
    newElements: "a",
    rangeSelection: .rightEdge,
    expected: "meela",
    closedExpected: "meea"
  ),
...

In fact, I've found comma separators annoying enough in the past that I use multi-line string literals and a custom .lines() to avoid them! String comparison benchmarks from back in the 4.1 days:

    payload: """
      café
      résumé
      caférésumé
      ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º
      1+1=3
      ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹
      ¡¢£¤¥¦§¨©ª«¬­®
      »¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍ
      ÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãä
      åæçèéêëìíîïðñò
      ÎÏÐÑÒÓÔÕÖëìíîïðñò
      óôõö÷øùúûüýþÿ
      123.456£=>¥
      123.456
      """.lines()

I found this pattern helped clarity (thanks to awesomeness of multi-line string literals), even though it was slightly icky and had runtime overhead (which we make sure to do prior to measuring runtime).

@nate_chandler, if there is no cost to syntactic evolvability of Swift, consider me a convert.

11 Likes

I think the title of this pitch should specify that it's about multiline expression lists, just to avoid any misunderstandings.

Having worked with the Squirrel scripting language, for which commas are optional in multline dictionary construction, I appreciate this pitch. I'll be honest--commas can become ugly and annoying in long lists, such as XCTest's allTests.

However, the strong opposition from @Chris_Lattner3 gives me second thoughts. Would this really make evolution of the language more difficult? Is it possible to think of a contrived example to illustrate this? Are there any other potential issues?

2 Likes