Confusing opening existentials syntax

From "Opening existentials" in the Generics Manifesto:

if let storedInE1 = e1 openas T { // T is the type of storedInE1, a copy of the value stored in e1
  if let storedInE2 = e2 as? T {  // Does e2 have type T? If so, copy its value to storedInE2
    if storedInE1 == storedInE2 { ... } // Okay: storedInT1 and storedInE2 are both of type T, which we know is Equatable
  }
}

What is the difference between e1 openas T and e2 as? T?
Or in pointfree notation: what is the difference between openas and as?/as!?

cc @Douglas_Gregor @dabrahams

I believe what’s happening here is that the openas operation is creating some dynamic type T and once it’s binded the value’s dynamic type to a local type T then you can just use the regular cast operation to see if the other value is T.

2 Likes

The T in openas T is declaring a new type T. It would be more clear to make the declaration of T explicit, e.g., e1 openas<T> T.

Doug

5 Likes

What about something like this, with an explicit type "placeholder" on the left of the assignment?

if let storedInE1: <T> = e1 {
  //...
}

I like the direction this is in. I would still like an explicit open operation:

if let storedInE1<T>: T = e1 openas T {}

:man_shrugging:

I suggested the following syntax in the opaque types manifesto:

4 Likes

Cool, now I also understand that part of the manifesto. And thank you @Karl for sharing the example. It makes more sense to me on how opening existentials could potentially work. :+1:

1 Like

Opening existentials is not really a conditional operation, so associating it with if let is probably confusing as strawman syntax. Something like Karl's syntax makes more sense to me.

My own preference would be that existentials automatically open, and that we have some sort of syntax for expressing types relative to existential let bindings. So you could say:

let objects: Collection = ...
let destination: Collection = ...

if let destination = objects as? type(of: objects) { ... }
8 Likes

This is exactly what confused me. Let's fix in this in the manifesto and improve the comments :slightly_smiling_face:

This now makes sense! Especially with Joe's clarification.

That would be cool, but it seems to only work well for comparing types which are exactly equal. If you’re checking multiple associated types, it becomes quite verbose and would require some kind of flow-tracking to keep track of which constraints you’ve tested for each existential:

let objects: Collection = ...
let destination: Collection = ...

if type(of: destination).Element == type(of: objects).Element {
 // compiler needs to keep track of the above constraint
 let sameContents = objects.elementsEqual(destination)
}

It is quite nice and approachable, though. It helps avoid some of the “angle-bracket blindness”.

If we borrowed Scala’s syntax for path-dependent types then we could say destination.Element and objects.Element. I think this is a good direction.

3 Likes