[swift-evolution] [Manifesto] Completing Generics

Dave Abrahams dabrahams at apple.com
Fri Mar 4 10:09:48 CST 2016

on Thu Mar 03 2016, Brent Royal-Gordon <swift-evolution at swift.org> wrote:

>>> The choice of Equatable as an example for opening existentials is
> an
>>> interesting one here, because it's one of the few cases I can think
> of
>>> where differing dynamic types is actually fully defined: they're
> not
>>> equal. In ObjC we express that by starting every -isEqual:
>>> implementation with if (![other isKindOfClass:[self class]]) {
> return
>>> NO; }, which while clunky and easy to forget, does neatly express
> the
>>> desired semantics with no burden at the callsite.
>> It's a reasonable default, but it's not necessarily the right
>> implementation for every type.  One could imagine Polygons comparing
>> equal to Squares, for example.
> If the "open" operation is sophisticated enough, when it's faced with
> mismatched concrete types but they both conform to a protocol with a
> existential that meets the requirements, it could return that
> existential.
> That would mean that, in this scenario:
> 	protocol Shape: Equatable { ...}
> 	protocol Polygonal: Shape {
> 		var vertices: [Vertex]
> 	}
> 	struct Square: Polygonal { ... }
> 	struct Polygon: Polygonal { ... }
> You could add this:
> 	extension Any<Polygonal>: Equatable {}

If you could extend just the existential, yes, that could work.  I don't
think this requires any “opening” though, and it's not taking advantage
of the default behavior for equatables of different types, which is my

> 	func == (lhs: Any<Polygonal>, rhs: Any<Polygonal>) -> Bool {
> 		for (lhsVertex, rhsVertex) in zip(lhs.vertices,
> rhs.vertices) {
> 			if lhsVertex != rhsVertex {
> 				return false
> 			}
> 		}
> 		return true

I think you'd need to sort the vertices or something, but I get the point.

> 	}
> And then, if `==(_: Any<Equatable>, _: Any<Equatable>)` were passed a
> Square and a Polygon, opening the `Any<Equatable>`s would give you a
> pair of `Any<Polygonal>`s.
> This is obviously more complex than simply opening the existential,
> matching its concrete type against a requirement, and extracting the
> original value if it matches—it's looking at the concrete types of N
> existentials, simultaneously matching them *all* against a requirement
> to find a more specific type they can all be cast to, and then
> performing that cast. To do its job, it would need to see all of the
> operands at the same time and evaluate them together to find a type
> that would fit all of them.
> That's what I was trying to get at when I wrote this example earlier
> in the thread:
> 	func == (e1: Any<Equatable>, e2: Any<Equatable>) -> Bool {
> 		guard let concreteE1<T: Equatable> = e1 as? T,
> concreteE2 = e2 as? T else {
> 			return false
> 		}
> 		return concreteE1 == concreteE2
> 	}
> If both parameters were `Square`s, then `T` would be a `Square`. But
> if one was a `Square` and the other a `Polygon`, `T` could be an
> `Any<Polygonal>`. Because you are simultaneously matching both values,
> you don't have to try the match both ways, and the operation is free
> to return a more specific protocol existential if that's the best it
> can do.

Ah... finding a unique possible common equatable type sounds both
inefficient and like it could fail at runtime due to ambiguity.  I'd be
at least very cautious about creating a system like that.


More information about the swift-evolution mailing list