[swift-evolution] Making protocol conformance inheritance controllable
jgroff at apple.com
Tue Dec 15 16:14:14 CST 2015
> On Dec 14, 2015, at 6:53 PM, Greg Parker <gparker at apple.com> wrote:
>> On Dec 10, 2015, at 18:04, 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
>> 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:
>> and Karl Adam trying to do the same:
>> 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.
> I don't like it.
> The problems in the motivating examples all center around protocols with factory methods or other uses of Self. Changing the behavior of all protocols seems like the wrong tool to solve that problem. There must be a narrower solution that improves those cases without changing so much existing code and understanding. (Are there any other languages with similar behavior?)
The change in inheritance behavior could be constrained to only protocols with factory or initializer requirements, since it makes no difference whether the conformance is inherited if there are no covariant uses of Self in the protocol.
> The "NSCoding is annoying" example is particularly dangerous. Historically the alternatives have been:
> * Don't require subclasses to implement init(coder:). This leads to difficult bugs where a subclass does not implement init(coder:) but somebody tries to encode and decode an instance of it. There are no compile-time errors, no runtime errors, and the resulting decoded object is corrupt.
> * Require subclasses to implement init(coder:), even if all it does is abort(). This provides good runtime diagnostics against the bug above, at the cost of some subclass boilerplate. This is what Swift does, and what ObjC now does with the new designated initializer enforcement.
> I fear that non-inherited protocols will reintroduce the old bugs. The subclasses will not be required to implement init(coder:), and encoding and decoding an instance of such a class will do corrupt things at runtime with no runtime diagnostics.
You're right, NSCoding is probably a legitimate example of a case where some class hierarchies should enforce refinement by subclasses. Even here, though, there are class hierarchies where a common NSCoding implementation would be appropriate. Do NSString or NSArray subclasses usually provide their own NSCoding implementation?
More information about the swift-evolution