SE-0335: Introduce existential `any`

Perhaps we disagree on what “the idea” is. Swift programmers don’t need to understand “the idea” of existential type quantification. They do need to understand the idea of a data type that can store any value that conforms to a protocol. You can implement that second concept without understanding type theory or predicate logic.

1 Like

That matches what I said. Any is an empty protocol composition, i.e. protocol<>. A typealias to something that is not a nominal type is also not a nominal type.

I was not suggesting making Any a nominal type. I was pointing out that the Any syntax looks like a nominal type, which is misleading.

I don't agree with this. Nearly all wrapper types with the naming convention AnyP, e.g AnyView, AnyHashable, etc, all conform to the protocol.

This is not a comprehensive way to check if a type conforms to a protocol, because protocol conformances can be declared in extensions.

Yes, existential types effectively provide subtype polymorphism. That is distinctly different from parametric polymorphism, which is what generics provide, including type parameters and opaque types.

No, with the Any<P> syntax, there still needs to be two different ways to spell the different metatypes that you can get with P. Any<P>.Type would be the singleton metatype, and Any<P.Type> would be the existential metatype. The difference between this and what's in the proposal is replacing parenthesis with angle brackets.

some P is exactly the same concept as a type parameter <T>. The only difference is where the generic argument is inferred from. Working with a return type of some P works the same way as working with a type parameter T inside a generic context. Both of these things are still types in the type system, even if they represent type "placeholders" that can be substituted with concrete types.

5 Likes

Agreed about “dynamic” for the reasons stated. However I think it’s clear from the proposal that surfacing a potential performance trap is absolutely a part of the motivation for this feature.

Read this part again from the motivation:

In addition to heap allocation and reference counting, code using existential types incurs pointer indirection and dynamic method dispatch that cannot be optimized away.
Despite these significant and often undesirable implications, existential types have a minimal spelling. Syntactically, the cost of using one is hidden

So the problem as stated is that a potential performance trap has too minimal of a spelling and we need to add new syntax to make this more clear. I just don’t see the word “any” achieving that goal whatsoever.

For example, a generic function can also accept “any” type conforming to P without the same performance hit. It’s not “any”-ness (polymorphism) that creates the performance trap, it’s how the polymorphism is achieved that makes the difference (generic specialization at compile time vs an existential container at runtime).

Furthermore, anyone who’s ever used a protocol already understands that “any” value conforming to P can satisfy a variable or parameter declared as P. Requiring the word any does not seem to communicate anything useful nor does it speak to the performance cliff that the author is worried about. I don’t see how the benefits of this solution meet the very high bar needed for source breaking changes.

3 Likes

From my initial review above:

See also the points in that post about how P : any P :: const x : let x, and about future directions.

¯\_(ツ)_/¯ Agree to disagree, I guess.

The amazingly useful thing it communicates is that when a new Swift user (or a former obj-c programmer, who tend to do this all the time with protocols in that language...) writes func f() -> P, that programmer will get an error. And that error will probably ask something about whether they intended some or any, and this is exactly as critical and as useful to learning as the programmer who uses an optional for the first time and is asked whether they intended ? or !.

That is, as a signpost that there is something new to understand here.

11 Likes

In the world of Swift, some P is a type; how it’s represented in SIL is neither here nor there with respect to the user-facing model. [Edit: I see @hborla’s post above already answers this point in more detail.]

2 Likes

I’m not arguing against a keyword to denote an existential, just wondering if there’s a better word besides “any” that actually addresses all of the concerns in the motivation. If “P” is too minimal
a spelling for all of the performance implications, I would argue that “any P” is not any better in this regard.

1 Like

I don’t understand how you can come to this conclusion. func f() -> some P and func g() -> some P have distinct types. some P is a syntactic placeholder for the parts of those types that vary.

(edit) Perhaps there is an “is a” vs “is-a” confusion here…

1 Like

I mentioned this in the pitch but I will reiterate, "any P" would help dramatically when you are searching through a large project trying to find existential usage. If it's bare "P", you can't find the existential usage as easily.

2 Likes

It is definitely part of the motivation, but I agree with @Paul_Cantrell that the semantics and its fundamental limitations are far more important to prioritize in the syntax than the performance implications. Perhaps the language in the Motivation section is too strong on the side of performance and not strong enough with respect to semantics :slightly_smiling_face:

The value I see for any in terms of identifying possible performance bottlenecks is that it's a searchable keyword in a project. If you're profiling an app and you notice a lot of traffic caused by existential types, e.g. lots of existential opening, you can easily look at the problematic function and spot the any.

I mentioned this above, but it is impossible to communicate everything one needs to know about a feature in the syntax. There will still be a learning curve, and that's okay. But as others have noted, having an explicit keyword will help people find the appropriate documentation, especially if we had documentation for keywords themselves, similar to the "educational notes" we have for explaining specific error messages.

EDIT: That said, I'm happy to discuss alternatives to "any", but I think "existential" is too jargony, and I don't think "dynamic" is better both because it doesn't help convey the limitations and because it's already used as a keyword, like you mention

8 Likes

Another great point but I’m not arguing for bare P, but rather a different keyword than any.

Ok, I guess I was addressing this specifically:

"any P" while still minimal, is significantly better than bare P for the reason I mentioned.

I'm thrilled to see this proposal, it will really help clarify the language model going forward. +1 from me.

-Chris

4 Likes

Thanks very much for the reply! Certainly it’s impossible to communicate everything via syntax but there are some syntaxes that communicate more than others, all else being equal. I do see the nice dichotomy between some and any which makes their incompatibility intuitive.

Perhaps I got too focused on the performance implications, in reality anyone who is optimizing for performance probably already understands the overhead of using a protocol vs a known type or generic specialization. Or at least they’re going to need to do out of band learning anyways to understand that, the syntax alone will not teach them as you said.

My last remaining point that I think still stands is that the searchability of “any” with the overlap of Any, differing only in capitalization, is going to frustrate the learning efforts of some developers. I understand these two concepts are closely related but there will be some inevitable confusion I’m afraid.

Either way, I change my -1 to +1 as I now understand the primary motivation is less about making performance cliffs obvious and more about notifying the user that there’s more to understand than bare P really communicates. Learning about existentials and their performance implications can follow from there if necessary.

5 Likes

The latter does not need to be generic at all, nor any type which uses <>. It's parameterized but the final type is not guaranteed to be a generic type.

typealias Foo<T> = T
2 Likes

+1 to the proposal on all counts but one - the exceptional behaviour of Any and AnyObject. I'd like to weigh in favour of making Any and AnyObject typealiases along the lines of any Thing and any Object as it would make their otherwise exceptional behaviour much more predictable, as they'd be following the behaviour laid out for typealises more generally in the proposal.

Were any and some required on these types, I could see some having a few use-cases alongside AnyObject when deinit or weak-reference behaviour is needed (untyped sets?), and being able to use any Object and some Object just flows really naturally and feels more Swifty than the alternative.

Also, I just really like how any Thing and some Thing flow, so I'm pitching that as a any/some-compatible alternative to Value which doesn't imply value-semantics.

4 Likes

I’m not sure what you mean by “parameterized but not guaranteed to be generic.” This feature you show is known as a generic type alias (SE-0048). In fact the proposal literally says of the feature:

This is consistent with the rest of Swift's approach to generics, and slots directly into the model.

And so, by analogy, @gregtitus’s point would be that the proposed Any<T> would not “slot[…] directly into [Swift’s generics] model.”

To expand on this a bit, the syntax symmetry is also important for future extensions of the some and any syntax. Opaque types and existential types would both greatly benefit from being able to specify constraints on associated types. This could naturally be done in angle brackets, e.g. some Sequence<Int> and any Sequence<Int>, or the suggested alternative of some Sequence<.Element == Int> (please don't bikeshed this here, if you're passionate about this feature, see [Pitch] Light-weight same-type constraint syntax :slightly_smiling_face: ).

The Any<P> alternative to any P would necessitate either using nested angle brackets, e.g. Any<Sequence<Int>>, or inventing two different syntax extensions to allow constraints on both opaque and existential types, e.g. Any<Sequence where .Element == Int> vs some Sequence<.Element == Int>.

Using the same syntax between these two different concepts also makes it really easy to replace any with some, and it is indeed the case that many uses of existential types today could be replaced with opaque types instead. This will be even more relevant if we extend some to parameter position to enable implicit type parameters in generic signatures for functions.

18 Likes

This would be nice addition to the proposal documentation to motivate the chosen keyword…

4 Likes

Agreed, I've opened a PR to add this justification to the proposal here: [SE-0335] Proposal clarifications based on review feedback. by hborla · Pull Request #1492 · apple/swift-evolution · GitHub

3 Likes