[Completing Generics] Opening existentials

(Jonathan Tang) #1

Any chance we could have the as? operator used for opening existentials
instead of introducing a new openas one?

My reasoning is that the usage for opening an existential is very similar
to the usage for downcasting to a subtype. In both cases, the initial
variable may be one of a large number of possible values, and you are
conditionally testing for membership in a subset of those values. With the
downcast, the subset is a subtype. With the existential, the subset is a
single type in the type family. Indeed, in many cases your "test for a
concrete type conforming to a protocol" became a "test for a dynamic type
in an existential" because you added an associated type or Self reference
to your protocol. Syntactically, the rest of your program is identical.

I don't *think* there's any chance for ambiguity here: right now, you can't
even create a value of type Equatable (since it can only be used as a
generic constraint), so these are completely disjoint situations.

It'd also be neat if there was a standard library function for "if two
objects are the same type and both equatable, compare with ==. Otherwise,
return false." This seems to be by far the most common definition of
.equals in languages in languages that let you override it (Java, Python,
etc.) Maybe as a default method on Equatable?

As a side note, will existentials mean that protocols can finally be useful
in Sets
Since Set requires that its element be Hashable yet adopting Hashable
requires that the protocol can only be used as a generic constraint, you
can't really put a bunch of objects all conforming to a protocol in a Set
and do anything useful with them. This is a really common pattern for eg.
having a bunch of objects that all do something and wanting to apply all of
their effects.


On Wed, Mar 2, 2016 at 5:22 PM, Douglas Gregor via swift-evolution < swift-evolution@swift.org> wrote:

*Opening existentials*

Generalized existentials as described above will still have trouble with
protocol requirements that involve Self or associated types in function
parameters. For example, let’s try to use Equatable as an existential:

protocol Equatable {
  func ==(lhs: Self, rhs: Self) -> Bool
  func !=(lhs: Self, rhs: Self) -> Bool

let e1: Equatable = …
let e2: Equatable = …
if e1 == e2 { … } *// error:* e1 and e2 don’t necessarily have the same
dynamic type

One explicit way to allow such operations in a type-safe manner is to
introduce an “open existential” operation of some sort, which extracts and
gives a name to the dynamic type stored inside an existential. For example:

if let storedInE1 = e1 openas T { // T is a the type of storedInE1, a
copy of the value stored in e1

  if let storedInE2 = e2 as? T { // is e2 also a T?

    if storedInE1 == storedInE2 { … } // okay: storedInT1 and storedInE2
are both of type T, which we know is Equatable