Well, as outlined before we have to be careful on how we involve any in the context out meta types. The proposed (any P).Type does already break the ideas from the UX discussion, as any P.Type was meant to become the "existential of the protocol P metatype".
If on the other hand we'd design it in an additive manner, then I'm fine deferring this topic. I have a fear that if we don't solve it with any at all, or if we go the with the pitched direction in the context of meta types, the chances on when we can resolve the issue with meta types might shrink down to zero.
Personally I would love to see a meta keyword and finally free up the .Type space for custom nested types called Type, however this is such a massive source break. :(
One future direction from our old proposal was this:
func subtype<T>(of type: Type<T>, named: String) -> AnyType<T>? { ... }
// with the idea of a `meta` keyword this would be
func subtype<T>(of type: meta T, named: String) -> (any meta T)? { ... }
// with the spelling from Joe Groff this would become
func subtype<T>(of type: (any T).Type, named: String) -> (any P.Type)? { ... }
I wonder if it could be as simple as allotting an entire major version (Swift 6) to be a transitional period where bare P-as-existential leads to only a warning with a fix-it.
There may be value in studying in detail how Rust made the transition also (I have not yet had opportunity to read the linked document that is undoubtedly illuminating).
Sorry, I probably should have written this in alternatives considered - this is a really enticing idea, and I tried really hard to make it work, but it leads to a pretty confusing programming model because you need to have a really deep understanding to know when you can write P and when you need to write any P.
This is true for function parameters, but it is not for stored properties. A stored let-constant with existential type cannot change dynamically for a single instance of that type, but the underlying type can certainly change across instantiations of that type, which means the type must be parameterized on that underlying type in order to preserve concrete type information, which is a much bigger semantic difference than using an existential type. These semantic changes also necessitate an ABI change.
That said, I really like the idea when I think about it with a different framing. What you're suggesting is effectively to be able to use a plain protocol name to mean an implicit type parameter conforming to the protocol. I don't think we should encourage people to think of this thing as a "special" kind of existential - people should think of it as a type parameter. This is what lead me to the future direction to allow this. It's effectively what we've been talking about doing with some, but not needed the some keyword.
I agree that a period of time when this is a warning with a fix-it would help with the transition.
Yeah, this is really only appealing to me if the "require any for all existentials" is considered too large of a break.
Ah, good point.
Cool, I like that a lot. If we can go the route of completely deprecating bare existential syntax, then reintroducing the bare protocol name as shorthand for an implicit, anonymous type parameter in Swift X1 would be great.
I've actually updated my previous message as I had things wrong interpreted by the pitch. I'm against the meaning of (any P).Type as proposed. This goes against:
And for more context on this issue here are a few examples:
protocol P {}
struct S: P {}
type(of: P.self) // P.Protocol.Type
let p: P = S()
type(of: p) // S.Type
type(of: p) is S.Type // true
type(of: p) == S.self // true
P.self is P.Type // false
P.self is P.Protocol // true
func whatAmI<T>(_ t: T.Type) {
print(type(of: t))
}
whatAmI(P.self) // P.Protocol
whatAmI(P.Type.self) // P.Type.Protocol
whatAmI(P.Protocol.self) // P.Protocol.Type
whatAmI(Any.Type.self) // Any.Type.Protocol
whatAmI(AnyObject.Type.self) // AnyObject.Type.Protocol
// True `P.Type` is not accessible today.
Here are some sub-type relations between existential types and also normal and existential metatypes. Let's say T is a non-protocol type and P is a protocol.
// For non-protocol types those types are sub-types of themselves.
any T : any T
(any T).Type : (any T).Type
any T.Type : any T.Type
// In case of protocols this is a bit different.
any P : any Any
(any P).Type : (any Any).Type
any P.Type : any P.Type
I know this topic is mind bending, but it is what it is and we eventually have to talk about it.
I’m of the understanding that the pitched meaning for (any P).Type is exactly what @Joe_Groff describes in the text you quote (“the type of the existential itself”). Indeed I cannot contemplate that spelling meaning anything else.
So long as that is agreed, everything else about changing metatype syntax can be a separate proposal—I would be strongly opposed to tackling any of that in one go as it’s an extensive topic of its own, as you well know.
I am now thoroughly confused, but my read is that Joe has made several distinct suggestions, and what you quote is one that could be “if we say that P.Type is a generic constraint…” I do not think we need to say anything about that necessarily as part of this proposal.
Here is the only scheme my tiny mind can understand; I had thought that this was what was pitched:
(any P).Type is the (meta)type of any P, the existential type.
P.Type is an existing spelling; we do nothing with it.
any P.Type is not part of this proposal, because P.Type is not itself an existential type (per item 2).
This would not preclude or limit the design space for any other examination or overhaul of metatypes so long as we are agreed on item 1, since the rest is just the status quo.
protocol P {}
// Joe's suggestion: `(any P).Type` vs. this proposal `P.Protocol`
let _ = P.self // "static" metatype
// Joe's suggestion: `any P.Type` vs. this proposal `(any P).Type`
let _: P.Type = ...
^~~~~~ // "existential" metatype
Sorry, I do not have the faintest idea what any part of this example means.
I don't want this to sound like a drama, the proposal authors have to clarify this on us. It could just be a wording issue in the proposal where they actually meant to say "(any P).Type is the (meta)type of the protocol existential" instead of the "metatype existential" which are two different things.
In fact the "existential metatype" is probably better spelled as any (any P).Type.
If we're deprecating P as a type (for the time being) as part of this proposal, IMO we should also deprecate P.Type. It doesn't make much sense to me to have a P.Type when, indeed, P is not a type!
I had suggested in the pitch for the "existential metatype", i.e. the type that can store any concrete metatype conforming to the protocol, be spelled as (any P).Type. It made sense to me because of the way subtyping works with metatypes, but I can understand why folks would want this to be spelled as any P.Type. I agree with @Jumhyn that (any P).Type and any P.Type should not mean two different things because the syntactic distinction is far too subtle. In any case, I do not think that this proposal should change the .Protocol syntax, aside from perhaps making it (any P).Protocol.
One alternative spelling that has been mooted in the past is Any<P>. Can we spare a few words in the Alternatives Considered section as to why any P is superior? Any<P> has the benefit of more clearly connoting the “existential box”.
Well I remain in a strong disagreement. This proposal says that "existentials" should receive the any keyword, however it mixes up what an "existential metatype" is and does not add the promised any keyword to metatype existentials themselves.
As I already said, I understand that this area of the language and discussion is extremely confusing to most of the readers. I spent days figuring out these tiny details. So I apologize for the caused disruption and confusing to everyone. However, it's on the table and I speak up, because I care about this topic, as I already did 5 years ago.
That said I would like to pivot the direction of this proposal to this:
// `T` is a non-protocol type, and `P` is aprotocol
let _: T = ...
let _: P = ...
let _: any P = ... // already proposed
let _: T.Type = ...
let _: T.Type = ... // okay as it request `T.self`
let _: any T.Type = ... // explicitly requests the "existential metatype for T"
let _: P.Type = ...
let _: any (any P).Type = ... // the existential metatype of a protocol existential
To be clear, I'm open to spelling the existential metatype as any P.Type, but I don't think (any P).Type should be valid and mean something different. I see three options:
Allow (any P).Type and any P.Type to mean the same thing
Only allow any P.Type and ban (any P).Type
Only allow (any P).Type and ban any P.Type (currently in the proposal)
I'll take some time to process the discussion here, consider these alternatives, and write them out in the proposal. Thanks all for your feedback so far!
But more importantly, it would be misleading, implying that it's a generic type and that you could, in theory, write it yourself similar to how you can write Optional<T> yourself. But that's not true: this will be a built in compiler feature you would find very hard to replicate. any is like as or for... you can get close to constructing something similar, but fundamentally these are built-in language capabilities.
I am not sure this needs to be in "alternatives considered"... the word considered here needs to mean actually considered. Not "alternatives that could possibly be envisioned". This is kinda halfway.
I'm strongly in favor of this, and I might not have been just a few years ago, so I think it might be worth outlining why.
There's a pattern I've seen several times now where a library author will design and implement their API, and then start iterating on performance optimization and discover that it's nigh-impossible to hit their performance targets without a major redesign.
If they had been undisciplined about performance ("oh it's fine, we'll ship 1.0 in a usable-but-slow state, and then optimize"), they might have found themselves having to deprecate and replace already shipped API, which is messy at best.
This is particularly common with people doing their first ObjC -> Swift switch (since ObjC assumes all values are pointer sized and respond to objc_msgSend, protocols and objects are equally [in]efficient), but it can easily happen to anyone.
Just like in cooking, a little syntax salt often helps the syntax sugar taste all the sweeter
I would disagree with this, particularly if it is accepted—as it seems it will be—that (some P)? should not be spelled as some P? since the latter is disallowed on account of being some (P?).
Rather, I would expect any P.Type to be equivalent to any (P.Type) based on the same reasoning, which would then be distinct from (any P).Type so long as the “type that can store any concrete metatype conforming to the protocol” is not the “metatype of the existential type.”
So I guess here the point of disagreement is as @DevAndArtist states, which is that we do not all have consensus as to what (any P).Type means.
Edit: And given that @Jumhyn makes a good point that we should deprecate P.Type if bare P is no longer a type, I also agree with @DevAndArtist’s point that the “type that can store any concrete metatype conforming to the protocol” would be spelled then any (any P).Type so long as we allow (any P).Type to have its natural meaning of the “metatype of the existential type any P”—and we could therefore disallow any P.Type just as we disallow some P?.