At first glance, I love it. This feels like something that should "just work". A couple questions that came to mind as I read through:
func checkFinaleReadinessOpenCoded(costumes: [any Costume]) -> Bool {
for costume: some Costume in costumes { // implicit generic parameter binds to underlying type of each costume
let costumeWithBells = costume.withBells() // returned type is the same 'some Costume' as 'costume'
if !costume.hasSameAdornments(costumeWithBells) { // okay, 'costume' and 'costumeWithBells' have the same type
return false
}
}
return true
}
Is there a reason we need the some Costume
here? IMO it would be great if we had this behavior out-of-the box without requiring users to specify a type annotation in the for
patternâI suspect many Swift programmers don't even know this is possible. But I haven't thought through whether auto-opening existentials in for
loops would have the same change-of-behavior issues as noted below, or whether there are any trapdoors to avoid those issues.
My primary concern with this proposal is the failure cases:
func cannotOpen1<T: P>(_ array: [T]) { .. }
func cannotOpen2<T: P>(_ a: T, _ b: T) { ... }
func cannotOpen3<T: P>(_ values: T...) { ... }
struct X<T> { }
func cannotOpen4<T: P>(_ x: X<T>) { }
func cannotOpen5<T: P>(_ x: T, _ a: T.A) { }
func cannotOpen6<T: P>(_ x: T?) { }
func testCannotOpenMultiple(array: [any P], p1: any P, p2: any P, xp: X<any P>, pOpt: (any P)?) {
cannotOpen1(array) // each element in the array can have a different underlying type, so we cannot open
cannotOpen2(p1, p2) // p1 and p2 can have different underlying types, so there is no consistent binding for 'T'
cannotOpen3(p1, p2) // similar to the case above, p1 and p2 have different types, so we cannot open them
cannotOpen4(xp) // cannot open the existential in 'X<any P>' there isn't a specific value there.
cannotOpen5(p1, p2.getA()) // cannot open either argument because 'T' is used in both parameters
cannotOpen6(pOpt) // cannot open the existential in '(any P)?' because it might be nil, so there would not be an underlying type
}
The failures here are, IMO, even more subtle than the blanket 'P' as a type cannot conform to itself
error, so I am slightly concerned that we'd be leading the user even further down the path of using existentials only to find at an even later point that their type design might not work. I don't really have suggestions here, and my hunch here is vague, but I wanted to raise it for discussion. (And I think the call on opening optional existentials is probably correct, for the same reasons.)
I think it's probably the case, though, that the problem more-subtle error cases is outweighed by the benefit this would bring in the common cases.
However, because the return value is permitted a conversion to erase to an existential type, optionals, tuples, and even arrays are permitted:
This list is non-exhaustive, I assume (i.e., other existing conversions here will be supported as well)?
This proposal has two effects on source compatibility. The first is that calls to generic functions that would previously have been ill-formed (e.g., they would fail because any P does not conform to P) but now become well-formed. For the most part, this makes ill-formed code well-formed, so it doesn't affect existing source code. As with any such change, it's possible that overload resolution that would have succeeded before will now pick a different function.
...
The second effect on source compatibility involves generic calls that do succeed prior to this change by binding the generic parameter to the existential box.
Changing the meaning of existing code seems undesirable to me. Is there any reason why this proposal wouldn't adopt the appropriate ranking rules to maintain existing behavior? We could prefer overloads which don't require existential opening to those that do, and prefer to bind a generic argument to the existential over the underlying type when both are possible. I know that at least on the second front the proposal says that the change in semantics is probably a win, but I'm still a bit wary of phrases like "unlikely to be affected"...
It seems like we could at least error/warn about these potential ambiguities and then offer "as P
" or "as some P
" to force the user to disambiguate themselves, while still getting most of the "wins" in the common, non-ambiguous cases.
Overall, I'm really excited by this!