[Pitch] Elide `some` in Swift 6

Before discussing performance differences between any and some, I think we should first establish if this is even relevant to the pitch. As far as I know, it has never been proposed before, even casually, to change the meaning of an existing syntax, in order for a subset of programs that use that syntax to get a performance improvement.

I don't think it's to change the meaning of existing code for performance reasons in the first place, and here it is done for the the benefit of a fraction of a fraction of developers - those that are misusing the syntax!

So I have to ask: is existentials abuse such a rampant problem that this should even be taken into account?

Sadly yes

8 Likes

it can be simultaneously true that it is indeed a "rampant problem" and that changing the meaning of everyone's code beneath their feet is the wrong solution to that problem.

i imagine this statement is not exactly supportive of the language workgroup's mission to civilize. but i feel strongly that there is no substitute to teaching people to use some and any correctly.

8 Likes

I’m –1 on this change. Learning the difference between existentials and generics/opaque types is fundamental to doing anything more advanced in Swift. The introduction of some and any has been immensely helpful by making the distiction much clearer in the code. I really think they are an achievement in language design, especially together with all the other improvements to generics and existentials. So while eliding some would definitely make code more concise, overall, I don’t think it would make it more readable or understandable.

I also don’t believe that sweeping the distinction between naked protocol and some or any back under the rug will help learners. Yes, it might flatten the learning curve at the very beginning since you wouldn’t have to learn about some to write your first SwiftUI app. But when you do get to learning about existentials and generics (and you must at some point if you want to be proficient in the language!) the language would give you less scaffolding to build your understanding on top of. Every time you see a naked protocol P you now have to remember “oh, that’s equivalent to some P which means its generic or an opaque type”. That’s another mental step you have to take that hinders, not helps, understanding.

You’ll also quickly run into situations like the one described by @Gerzer:

Here, using the naked protocol is a pedagogical disaster, giving the learner expectations that are completely off.

Finally, I don’t think the code churn savings are quite as clear as made out to be. It’s quite likely that this change would also introduce lots of code churn, as codebases with opaque types would be rewritten to use the naked protocol form. For example, I would expect the prevailing style in SwiftUI to become var body: View instead of var body: some View, and there’s a lot of SwiftUI code out there that that would ripple through. (Not to mention tutorials and Stack Overflow answers that would become outdated.)

24 Likes

I very much disagree that knowing the difference between existentials and generics is a fundamental Swift skill.

To support this claim, I'll just remind that we have been writing Swift even before "existentials" were a thing. Go back to some Swift 1, 2, 3 codebases, and the discussions we were then having. Basically people would not care until they had problems with "PATs" (protocol with association types – we hear very much less about them these days). And then they would find a workaround. In the process, some would grab a solid understanding of existentials, some would not.

In my personal case, it has been a multi-years process. If you think about it, there's a lot of Swift to digest in order to get a full picture of existentials. Swift is quite a special language, with its focus on static compiler information and static dispatch, the strict application of the Liskov principle, advanced type system, etc.

In this fuzzy context, the Core Team has slowly but steadily built the tools that brought us to the present time. More and more developers have strengthened their skills about generics and existentials. The Swift forums is a nest of experts.

Meanwhile many developers still don't quite care, and yet they produce valid Swift code that runs on countless devices.

In this context, I very much welcome the experiments performed by @johnno1962. He needs more support.

7 Likes

That's not really the issue here. The two main problems with the proposed change are:

  1. It creates a confusing situation where protocols can be used for three different things, and with three syntaxes but amazingly they would not correspond 1-1**
  2. The protocol usage that looks like a normal type (bare protocol) does not behave like a normal type

Both of these will be a great source of confusion, and (2) will in fact make if necessary to learn about generics sooner.

**
Use cases:

A. Existentials
B. Implicit generics ("some")
C. Declaring or stating conformance

Syntaxes:

a. any
b. some
c. bare protocol

a means A
b means B
c means B or C

10 Likes

I think the fundamental question motivating this change is: since the bare syntax for conformance’s and constraints isn’t going away, is the language better off where : SomeProto is valid in some places and not others? Or is it better to have : SomeProto work everywhere and do the “best” thing in each context? The proposal embodies the latter choice.

I think it's worth mentioning that even if we reject this proposal the bare protocol will still represent the generic option (option B) in the case of protocol extensions. I've seen some people floating the idea of going even further in the opposite direction of this proposal by changing the syntax of protocol extensions to extension some Equatable { }, which would officially make the syntaxes map 1-to-1 to the concepts.

4 Likes

That’s a good point, and I would say that this would definitely be an improvement.

It was very confusing for me what it actually meant to extend a protocol. My first thought was that it would add requirements, but that clearly wasn’t the case.

2 Likes

If the core argument for rejecting this proposal is that the conceptual honesty of requiring some or any is a better educator, that is in direct opposition to extension some Protocol { }. When you extend a protocol, you are adding methods or properties to all types that conform to that protocol. some Protocol is just one of those types.

You could write an entire program that never mentions the word some and still make use of protocol extension methods.

3 Likes

Hey everyone, thank you all for the discussion on this thread so far! Angela and I are reading every reply and making notes of the constructive points that folks have raised. I believe there are several excellent suggestions that should be pursued independently of this proposal -- such as allowing any P? -- and we're exploring possible solutions to the other usability issues with some P that have been raised here.

Of course, please feel free to continue the discussion, just wanted to let ya'll know that we're still listening :slightly_smiling_face:

22 Likes

some P is only one of those types at a time, but like any generic it conceptually iterates over all possible such types, so I don't feel that this is a strong argument. Except maybe against the name some.

1 Like

Everything in the extension is also available for objects declared as any Protocol, so I don‘t think some is a good idea in that position.

3 Likes

Maybe it’s relevant to this discussion to mention the notion of explicitly extending the existential of a particular protocol, which I believe must already be a plan for the future since I‘ve heard a lot about eventually declaring that the existential for a protocol conforms to the protocol.

I hadn’t thought about it until just now, but now that I have, it makes me consider more seriously whether changing the syntax to extension some Equatable might be a good idea.

Edit: Actually, I guess that’s almost a piece of evidence in favor of accepting this proposal, since in the future world where we can extend existentials of protocols then, on our current track, the two spellings would likely be:

extension MyProtocol { }
and:
extension any MyProtocol { }

Any Swift developer? The elementary school child who does the Learn To Code in Swift Playgrounds is a Swift developer now. I think that the distinction between Existentials and Opaque types are more advanced on the complexity scale and are certainly not in the "any developer should know" category.

3 Likes

Yes, the discussions here about benchmarking very specific scenarios, and pointing out that certain packages would still build if this change were made, strike me as a serious case of “your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should.”

6 Likes

I don't think that's quite true, extension SomeProtocol also affects existentials of that protocol. The distinction is more on the individual members, and how they interact with Self and associated types. For example, in the following code, only the last line doesn't compile:

protocol FooType {
  func frobnicate() -> Int
}

struct Foo: FooType {
  func frobnicate() -> Int {
    42
  }
}

extension FooType {
  func frobnicateAgain() -> Int {
    frobnicate() + 1
  }
}

let concreteFooType: Foo = Foo()
let existentialFooType: any FooType = Foo()

print(concreteFooType.frobnicateAgain()) // 43
print(existentialFooType.frobnicateAgain()) // 43

protocol BarType {
  func frobulate(with: Self) -> Int
}

struct Bar: BarType {
  func frobulate(with: Self) -> Int {
    42
  }
}

extension BarType {
  func frobulateAgain() -> Int {
    frobulate(with: self) + 1
  }

  func frobulateOnceMore(with other: Self) -> Int {
    other.frobulateAgain() + 1
  }
}


let concreteBarType: Bar = Bar()
let existentialBarType: any BarType = Bar()

print(concreteBarType.frobulateAgain()) // 43
print(existentialBarType.frobulateAgain()) // 43

print(concreteBarType.frobulateOnceMore(with: concreteBarType)) // 43
print(existentialBarType.frobulateOnceMore(with: existentialBarType)) // error: Member 'frobulateOnceMore' cannot be used on value of type 'any BarType'; consider using a generic constraint instead

I've been debating whether to reply to this message. It's been very perplexing to have what you've been trying to say so comprehensively dismissed but there are also times when the only winning play is not to play at all.

In the interests of clarity though even if some seem predisposed not to understand. IMHO:

  • Swift has always Elided to any.
  • SE-0335 removed that which is going to break a lot of source.
  • Migrating the elision directly to some is likely to make the situation even worse.

I have experimented with a partial change of the elision from any to some in specific cases only and the results are quite encouraging in terms of source compatibility. Eliding to some some, if you like.

I have attempted to benchmark whether there was a significant performance overhead to continuing to elide to any in some cases (storage) and it seems that is not the case.

3 Likes

It seems to me that this problem would be enough to disqualify the pitched change in language semantics. If that’s not the case, I’d love to understand why not.

5 Likes

The team knew this when SE-0335 was accepted, which must mean they were ok with that tradeoff. Avoiding problems caused by a pitch being accepted should not implicitly require the acceptance of other later pitches. Allowing that situation to occur only reinforces the perception that many reviews, especially around this time of year, are just formalities after it's already been decided that the pitch will be accepted.

Given that SE-0335 was accepted, along with the resulting source breaks, we must conclude that "there will be source breaks if we don't do this" cannot be a valid argument for this pitch, as it was not considered a valid reason to reject SE-0335 either. This pitch must be argued for on its own merits.

Then this pitch should not be accepted.

My point is that benchmarking something that we don't even know if we want to do cannot be used as an argument to do that thing.

"Should we send a rocket to the moon?"

"My simulations show it would be very fast."

Yes, but why is that fact a reason that we should do it?

7 Likes