[Pitch] Implicitly opening existentials

This is an interesting and attractive direction, but I share the concerns of @Jumhyn that it may lead users further down a path that initially Just Works, only to be forced to reckon with the distinction between existentials and opaque types a little later when they’re in deeper.

I see two parts to this proposal: (1) expanding where existentials can be opened beyond the protocol extension method trick—super great, hard to argue against, and on its own a purely additive change; and (2) doing this implicitly, with rules that are almost like a heuristic—which I see as separable and the source of much nuance and complexity (and, as noted in the text, possible source compatibility issues due to overload resolution changes).

The proposal justifies the implicit opening approach by characterizing an explicit opening alternative as “narrower” and saying “[a] programmer who has an existential and wishes to use a generic function would need to learn about opaque result types and their differences with existentials to do so” if the explicit opening alternative were instead adopted.

…And yet, the implicit approach pitched here is the one that includes the example @Jumhyn notes above, required so that 'costume' and 'costumeWithBells' have the same type:

for costume: some Costume in costumes {
  ...
}

To my mind, this explicit type annotation is really an explicit opening approach, and it demonstrates actually how explicit opening can be regarded as more general (not narrower) from that vantage—and, in that particular circumstance, what the user actually wants (as opposed to the implicit heuristic which erases the same-type relationship between ‘costume’ and ‘costumeWithBells’).

Elsewhere, I’ve proposed enabling not just a type annotation costume: some Costume but also expanding the use of as for this purpose. Notably, such a syntax would allow for explicit opening without creating a separate binding:

func hasExistentialP(p: any P) {
  takesP(p as some P)
  // No need to write:
  //   let openedP: some P = p
  //   takesP(openedP)
}

Without the friction of having to create a separate binding, I think it makes the explicit opening approach a viable alternative. Yes, users would have to distinguish any P from some P from the get-go, but the argument above is that users will often have to do so beyond the most trivial usages. If, later on, we find that the more general explicit opening approach is too wordy in a number of widely used scenarios, we could always come back to consider implicit opening as syntactic sugar, just like we have done for some P in parameter lists.

16 Likes