[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
point.
> 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.
--
-Dave
More information about the swift-evolution
mailing list