[swift-evolution] [Pitch] Achieving a heterogeneous collection of Equatables
Matthew Johnson
matthew at anandabits.com
Tue Dec 22 10:54:24 CST 2015
> On Dec 22, 2015, at 10:48 AM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
>
>
>> On Dec 21, 2015, at 1:45 AM, Roopesh Chander via swift-evolution <swift-evolution at swift.org> wrote:
>>
>> Hi all,
>>
>> We know that as of Swift 2.0, if a protocol uses `Self` (like
>> Equatable does), it cannot be used in a heterogeneous collection. This
>> is a pain point, and has been talked about many times (for instance,
>> in Brent Simmons' Swift Diary: http://inessential.com/swiftdiary).
>>
>> I realize that this problem is intertwined with the use of associated
>> types, but if we forget about associated types for the moment, I
>> believe we can get a heterogeneous collection of Equatable elements to
>> work.
>>
>> ## The problem
>>
>> There's no problem in creating a heterogeneous collection when there's
>> no `Self`:
>>
>> struct Collection<Element> {
>> }
>>
>> protocol LooseEquatable {
>> func isEqualTo(other: LooseEquatable) -> Bool
>> }
>>
>> let c1 = Collection<LooseEquatable>()
>>
>> The type characteristics of c1's elements can be deduced at compile
>> time, so this works.
>>
>> But when `Self` is used in the protocol:
>>
>> protocol StrictEquatable {
>> func isEqualTo(other: Self) -> Bool
>> }
>>
>> let c2 = Collection<StrictEquatable>()
>>
>> The type characteristics of c2's elements (for example, what are the
>> signatures of the methods that they should have) are indeterminable at
>> compile time because `Self` isn't yet "bound" to a type. For this to
>> work, the `Self` in StrictEquatable might have to bind to different
>> types at runtime for different subtypes of StrictEquatable.
>>
>> This, per my understanding, is why Swift errors out. Assuming this
>> understanding is correct, I'd like to pitch a solution.
>>
>> ## The pitch
>>
>> What I'm about to suggest is closely related to Joe Groff's suggestion
>> to use an EquatesWith here:
>> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/002300.html
>>
>> Let's say we introduce a new keyword called `Subtype` that can be used
>> when defining a protocol. `Subtype` binds to the closest subtype that
>> fully *implements* the protocol. (In contrast, `Self` binds to the
>> non-protocol type at the end of the protocol hierarchy.)
>>
>> For example:
>>
>> // Type hierarchy: P -> Q -> R -> S
>> // P, Q, R are protocols; S is a struct.
>>
>> protocol P {
>> func f1(t: Subtype)
>> }
>> protocol Q : P {
>> func f2()
>> }
>> protocol R : Q {
>> }
>> extension R {
>> func f1(t: R) { } // Implementing P
>> func f2() { } // Implementing Q
>> }
>> struct S : R {
>> }
>>
>> let s = Collection<S>() // Okay: P's Subtype is bound to R
>> let r = Collection<R>() // Okay: P's Subtype is bound to R
>> let q = Collection<Q>() // Error: Subtype cannot be resolved
>> let p = Collection<P>() // Error: Subtype cannot be resolved
>>
>> `Self` can only bind to a non-protocol type like S, but `Subtype` can
>> bind to either a protocol or a non-protocol type, depending on where
>> the protocol gets implemented. Here, the P protocol is implemented in
>> the sub-protocol R, and so the `Subtype` in P binds to R. If `Subtype`
>> cannot be resolved, it should result in a compilation error.
>>
>> In the standard library, if we replace all `Self`s in Equatable with
>> `Subtype`, we still maintain type safety (so `1 == 1.0` won't compile,
>> like it is now), but we will, at the same time, be able to create
>> heterogeneous collections of elements conforming to a sub-protocol of
>> Equatable, thereby fixing problems like this:
>> http://inessential.com/2015/08/05/swift_diary_9_where_im_stuck
>>
>> That said, while this conceptually looks good, I have no idea whether
>> it's practically viable. I'd love to hear the community's and the
>> compiler team's take on this suggestion.
>
> You wouldn't really need a different protocol keyword to achieve this. You're providing a conformance for the type protocol<R>: P, rather than saying T: P for all T: R, as happens today. That's a property of the conformance rather than the protocol itself. What you're proposing is closer to the other approach I laid out, allowing protocol types to be extended to conform to protocols themselves:
>
> extension protocol<R>: P { // We're extending the protocol *type* to conform, not its conformers
> func f1(t: R) { }
> }
I like it! Extend an arbitrary existential (and it’s conforming types?) with additional conformances. That is pretty cool!
More information about the swift-evolution
mailing list