Existential subtyping as generic constraint

But isn't this exactly what an 'existential' is - "type to that type or a subtype"?! Either that, or I completely don't understand something in that overlapping behavior.

I mean, we could also anchor the word 'existential' to the context of protocols, but judging from the overlapping behavior it feels like it's a generalized term of art for all of this.


One more thing, pun intended. What about such protocol?

protocol _UIView: UIView {}
extension UIView: _UIView {}

let view: any _UIView = ...

Isn't any _UIView like any UIView or just UIView in this case?


Also from the perspective of better UI for generics.

func foo(_ view: UIView) == func foo(_ view: any UIView)

func bar<T: UIView>(_ view: T) == func bar(_ view: some UIView)

To build a little further on that. We don't have the notion of 'exactly that type' in Swift.

     T // does not exist as 'exactly that type'
 any T // T or any of its subtypes (existential type) - other notation is our current `T`
some T // T or any of its subtypes (opaque type)

Just bike shedding but we could have final T if we really needed the notation of 'exactly that type', but there is probably no need for that at all.


PS: I'm just trying to understand some of these fundamental aspects. I'm not saying that I'm right and everyone else is wrong. I'm also learning and willing to change my mindset. :slight_smile: Sorry for kinda derailing you thread.

To get back on track I think what we really want to be able to express in this proposal is the difference between an 'existential' and a 'protocol' as a constraint.

protocol Proto {}
func f1<T: Proto>(_ t: T)     // T that conforms to protocol `Proto`
func f2<T: any Proto>(_ t: T) // T that is a subtype of `any Proto` existential

I view it this way: any keyword is used to turn non-type entity into an existential type. Class types are existential types by definition, so they don't need any keyword. We can agree that any applied to a type is a no-op, meaning that any MyClass is the same as MyClass in type context, but personally I'd prefer not to do this, his makes things confusing.

Oh, nice one! Here any _UIView and UIView are different things, but both are subtypes of each other.

Correct. Separating MyClass from any MyClass is an interesting topic. I haven't thought of it before. If you have arguments why this would be useful, I'd enjoy a separate thread on this. Note that any String is still not a thing.

No worries, we agreed on sending an MR to Generic Manifesto, and that's the action point I was looking for. So a bit of derailing does not hurt.

Exactly!

I assume it's not only about classes. I understand that any ValueType makes less sense, but it's not that unreasonable after all. We eventually want Never to become the bottom type. It means that ValueType is super type of Never and therefore Never: any ValueType is a valid subtype relation, even though just ValueType is a shorter notation. And we also want value type sub typing. @anandabits has written an excellent gist of his ideas in that direction: ValueSubtypeManifesto.md · GitHub

The only two places where separation of T and any T as 'exactly type T' and 'existential of T' makes sense is in generic context where you want to pass P the protocol vs. any P the existential or meta T (the final static meta type from T.self) vs. any meta T the existential metatype.

(I think for classes the ship has sailed and it would be a huge breaking change to differ Object vs. any Object, if we really wanted to.)

With generalized super type constraint this becomes a little more obvious:

struct G<T> {
  func foo<R: T>(_ r: R) {}
}

protocol P {}

let g1: G<P> = ...
let g2: G<any P> = ...

extension String: P {}

let p: P = "swift" // is `any P`

g1.foo(p) // not okay because `any P` does not conform to `P`
g2.foo(p) // okay now

One more thing about any Array. IIRC @anandabits wanted that notation to be able to express the following.

extension any Array {
  static var value = 0
}

print([Any].value) // 0
[Int].value = 1
print([Any].value) // 1
[String].value = 2
print([Any].value) // 2

It's the same static stored property on all possible parameterized Array types.


Long story short, I definitely see any and some be useful outside of protocols. That's about it. :smiley:

https://github.com/apple/swift/pull/28741

1 Like

I've got your point. Makes sense.

Thinking about Array, I've just realised that there are two kinds of existential here:

  • any Array - abstracts away generic parameter(s).
  • any Array<String?> - value subtyping
var x: any Array = ["abc", "xyz"]
x = [1, 2, 3] // OK
var xElem = x[0] // either error, or of type Any

var y: any Array<String?> = ["abc", nil, "xyz"]
y = ["abc", "xyz"] // OK, Array<String> is a subtype of Array<String?>
y = [1, 2, 3] // ERROR:  Array<Int>, is not a subtype of Array<String?>
var yElem = y[0] // .some("abc") of type String?

Exactly. If you want to apply the notion of existentials to types, it would be applied to generic types (i.e. type constructors) and we would have things along the lines of any Array. This same notion would apply to classes.

Do you know of any OO language which has such a notion? Do you know of any use cases for it? It doesn't sound like something that would be useful to me.

This is why I said upthread that we wouldn't be able to use existential subtype constraints without introducing the any syntax. I think it's rare that an existential subtype constraint would be useful anyway so I don't think it matters too much that we are currently unable to spell it.

What do you mean here? IMO, the any AnyObject syntax is clunky and confusing to the point that it might be enough to warrant deprecating AnyObject and replacing it with Object, which is already considered a bit of a wart.

Why would we want an existential in the latter case? It seems to me that importing language support for variance would be the more productive path there.

That's only in case if we separate exactly type T from T or any subtype of T. If there is no such separation, this is not applicable.

Actually the notion of exactly type T conflicts with the very essence of the subtyping mechanism. exactly type T is just an absence of subtyping. If you have types U and T which you would normally think of as being in a subtyping relation U: T, then for exactly type T to work, we would need the following:

T: any T
U: any U
any U: any T

But U: T should be excluded. And given that you still write literally U: T somewhere in code, this seems to create a huge mess.

BTW, looks like any P syntax is still not in the generics manifesto. I assumed it was.

It’s in the Improving the UI of Generics document which is kind of like the generics manifesto 2.0.

I don't, I was just thinking out loud, regardless its un-usefulness.

That's okay. It will be a consequence of having those two features implemented one day. It's worth pushing the general features though.

I wasn't speaking about AnyObject in particular. All I meant here is that SomeObject is basically the short form for any SomeObject, and it wouldn't be possible to change SomeObject to mean 'the exact class and nothing else', which I'm not pushing nor suggesting to do so. Again I was thinking out loud in that direction.

However I'm surprised to see a potential deprecation of AnyObject, but I'd support it in favor of any Object.

1 Like

I wouldn't think of it this way. I don't think there's a good reason to introduce the notion of existentials to OO subclassing.

It's probably not likely given the high bar of source compatibility. I only mentioned it because any AnyObject would be really clunky. I'm more aggressive about supporting refinements that improve the language even when they are source breaking than most people are and I don't make the call so you should take anything I say in this regard with a big grain of salt. :wink:

1 Like