More commas, fewer commas... no commas?

There's a few pitches discussing allowing trailing commas, and it made me wonder how people felt about no commas:
var foo = [
"bar"
"fum"
"quux"
]

This would strictly be allowed when entries are separated by newlines, a la result builders. It'd also reflect the newline-or-semicolon syntax for delineating statements.

This has been pitched and rejected. The simplest problematic example is something like this:

[
    foo
    .bar
]

Is that foo.bar or foo and a member of some type, Baz.bar?

6 Likes

Oh, as if foo were an simple variable, and .bar was a type function delivering an instance of the type.

Yeah, okay.

This was proposed in SE-0257, which was discussed here:

1 Like

This is a common problem I've seen emerge in programming language design, which is that the syntax has to be limited not by what a first-party compiler can understand, but what the common-denominator of third party tooling can handle.

Could the compiler be made to understand that syntax, e.g. when there's no local variable named foo with a .bar member that could be called on it? Yeah, but that's not something you could expect e.g. from the syntax highlighting on GitHub or even these forums.

As another example, this is why the operator and identifier characters are rigidly segregated, so that parsing Swift code doesn't require scanning for all the operator declarations.

4 Likes

Parsing happens first. The parser doesn't know about symbol visibility, so it can't tell if there's a foo in scope at the declaration.

3 Likes

I’d really like to see an official syntax for constructing array values using result builder syntax. This naturally avoids commas, but also has the huge benefit of letting you inline conditions into the array content in a much easier way than with existing array literals.

4 Likes

Keep in mind this "foo\n.bar" situation isn't a big deal in any case, because the compiler can always diagnose ambiguous intent and provide fix-its to add commas or parenthesis in. I usually use parenthesis in these multi-line situations anyway, even today. Especially for arguments, since it helps humans too and Xcode's built-in formatting behaves really poorly otherwise.

I tend to try and use syntax to make things clearer, and generally argue for fewer delimiters, but the tradeoffs are interesting.

For instance, I like the spare look of Python, whilst disliking the pasted-at-the-wrong-indent problem. I still really like the Pascal begin and end statements. On the other hand, I used to be fairly likely to use unneeded parentheses on mathy things to get the result I wanted, until my math and physics wizard sweetie helped me accept PEMDAS into my heart. That and a bunch of playing with things in Python and C.

I got a simple implementation from ChatGPT (and tested it first), is this the sort of thing you mean?

@resultBuilder
struct ArrayBuilder {
    static func buildBlock<T>(_ components: T...) -> [T] {
        components.flatMap { $0 }
    }
}

extension Array {
    init(@ArrayBuilder _ builder: () -> [Element]) {
        self = builder()
    }
}

// Example usage:
let arr = Array {
    1
    2
    3
}

print(arr) // Output: [1, 2, 3]

I guess you'd want to add the buildEither funcs and whatnot

Another way to characterize the desirable behavior you're describing is just that the language can be parsed into something approximating a reasonable parse tree without having to resolve names, perform complex semantic analysis, having to load other files, etc. Swift has this property while C does not.

8 Likes

Yeah. Within Airbnb we have an ArrayBuilder type like this that we use all the time. I'd love to see this built in to the standard library.

3 Likes

Result builder has to solve this case as well. Here's how it does it:

@resultBuilder struct StringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
}
extension String {
    static var foo: String { "hello "}
}
@StringBuilder func a() -> String {
    "hello"
    "world"
}
let x: String = .foo // βœ…
@StringBuilder func b() -> String {
    .foo // πŸ›‘ Reference to member 'foo' cannot be resolved without a contextual type
}

So the answer to your question above "Is that foo.bar or foo and a member of some type, Baz.bar?" could be simply:

[
    foo
    .bar // πŸ›‘ Reference to member 'bar' cannot be resolved without a contextual type
]
2 Likes

In abstract, yes. In practice, that would be a source-breaking change in case anyone is currently writing that for the last element of an array. (Whereas with result builders, they couldn't break any source, because they didn't previously exist.)

4 Likes

Right, which probably relegates this (formally) to a major release, and presumably not Swift 6 as that's fast approaching. Swift 7 is probably a very long way away, in contrast, alas.

But it could be made available in the interim through a feature flag.