[swift-evolution] Making protocol conformance inheritance controllable

Matthew Johnson matthew at anandabits.com
Thu Dec 10 21:03:36 CST 2015


I have run into this issue myself.  It is definitely a problem and we need a solution for it.  It is exacerbated by the fact that many Cocoa types which would naturally be value types or final classes in Swift are not designed this way due to their Objective-C heritage.  If Cocoa was designed in Swift and with an emphasis on composition rather than inheritance many cases of the problem would vanish and I’m not sure whether we would have a problem or not, but unfortunately we do.

My initial reaction to this solution is that it is probably a good approach.  One change I would consider is making it mirror method overriding a bit more closely.  Specifically, we might want to also make it possible to specify “final” protocol conformance as well as add the ability to “require" method overrides.  Aligning the capabilities would leave one less nuance for everyone to remember and reduce the complexity added by the proposal.

One alternative that might be worth exploring is whether we can introduce a concept supporting class clusters more directly as well as a FINAL annotation for Objective-C classes which really shouldn’t be inherited or where subclassing is of dubious value (inheriting to work around a bug is dubious IMO despite the practical value some see in it).  A combination of these two approaches might solve the problem for all or most of the classes we really care about.  I’m not suggesting this is a better approach, just sharing it for consideration.

Matthew


> On Dec 10, 2015, at 8:04 PM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I've had a number of twitter conversations with users who have valiantly fought our type system and lost when trying to make their protocols interact well with non-final classes. A few from recent memory:
> 
> - Rob Napier struggling to implement a `copy` method that works well with subclasses: https://twitter.com/cocoaphony/status/660914612850843648 <https://twitter.com/cocoaphony/status/660914612850843648>
> and making the observation that the interaction of protocol conformance and subtyping is very difficult to teach.
> 
> - Matt Bischoff trying to make Cocoa class clusters retroactively conform to his factory protocol:
> https://twitter.com/anandabits/status/664294382774849536 <https://twitter.com/anandabits/status/664294382774849536>
> 
> and Karl Adam trying to do the same:
> https://gist.github.com/thekarladam/c3094769cc8c87bf55e3 <https://gist.github.com/thekarladam/c3094769cc8c87bf55e3>
> 
> These problems stem from the way protocol conformances currently interact with class inheritance—specifically, that if a class conforms to a protocol, then all of its possible derived classes also conform. This seems like the obvious way things should be, but in practice it ends up fighting how many classes are intended to be used. Often only a base class is intended to be the public interface, and derived classes are only implementation details—Cocoa class clusters are a great example of this. The inheritance of protocol conformances also imposes a bunch of knock-on complexity on conforming classes—initializer requirements must be satisfied by `required` initializers (which then must be overridden in all derived classes, to the pain of anyone touching an NSCoding-inherited class), and methods often must return dynamic `Self` when they'd really prefer to return the base class.
> 
> To mitigate these issues, I'd like to float the idea that protocol conformances *not be* inherited by default. If you declare a class as conforming to a protocol, only exactly that class can be bound to a type parameter constrained by that protocol:
> 
> protocol Runcible {}
> class A: Runcible { }
> class B { }
> 
> func foo<T: Runcible>(x: T) {}
> 
> foo(B()) // calls foo with T == A
> 
> Since subclasses are still subtypes of the base class, in many cases client code won't have to change at all, since derived instances can implicitly upconvert to their conforming base class when used in protocol types or generics that only the base class conforms to. (There are cases like if the type parameter appears in a NonCovariant<T> type where this isn't possible, though.) Protocol requirements for a non-inherited conformance don't need to be `required` initializers, or maintain covariant returns:
> 
> protocol Fungible {
>   init()
>   static func funged() -> Self
> }
> 
> class C: Fungible {
>   init() {} // Non-required init is fine, since subclasses aren't directly Fungible
> 
>   // Non-Self return is fine too
>   class func funged() -> C { return C() }
> }
> 
> An individual subclass that wanted to refine the conformance could do so by `override`-ing it, and providing any necessary covariant overrides of initializers and methods:
> 
> class D: C, override Fungible {
>   // D must provide its own init()
>   init() { super.init() }
> 
>   // D must override funged() to return D instead of C
>   override class func funged() -> D { return D() }
> }
> 
> And if a class hierarchy really wants to impose a conformance on all possible subclasses, as happens today, we could let you opt in to that:
> 
> class E: required Fungible {
>   // init() must be required of all subclasses
>   required init() { }
> 
>   // funged() must return a covariant object
>   class func funged() -> Self { return Self() }
> }
> 
> This is undoubtedly a complication of the language, but I think it might let us more accurately model a lot of things people seem to want to do in practice with class hierarchies and protocols, and it simplifies the behavior of the arguably common case where inheritance of the conformance isn't desired. What do you all think?
> 
> -Joe
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151210/ff7d320a/attachment.html>


More information about the swift-evolution mailing list