[swift-evolution] [swift-evolution-announce] [Review] SE-0026 Abstract classes and methods

Brent Royal-Gordon brent at architechies.com
Tue Mar 1 04:29:55 CST 2016

>>> https://github.com/apple/swift-evolution/blob/master/proposals/0026-abstract-classes-and-methods.md

>> 2. Being able to use `override` and `super` in a protocol extension as long as the protocol has a must-inherit requirement ensuring the relevant member exists.
> Just a note: This would require naming the concrete super protocol to use, because multiple parents might provide the relevant member (and by virtue of protocol extensions they might even gain this member after the fact of defining the protocol containing the super call). Knowing which super to call is the advantage of and reason for the restriction to single inheritance when deriving from a parent class. 

I think you're talking about this problem:

	  1> protocol A {}; extension A { func foo() { print("A") } }
	  2> protocol B {}; extension B { func foo() { print("B") } } 
	  3> struct X: A, B {}
	  4> X().foo()
	repl.swift:4:1: error: ambiguous use of 'foo()'

That's not really related to the point above, which is about allowing protocol extensions to override superclass members.

	protocol ActivityViewControlling: UIViewController {
	extension ActivityViewController {
		override func viewWillAppear(animated: Bool) {

However, handling conflicts between protocol extensions with identically-named members *is* an issue with protocols in general. I think we should look into that as a problem with protocols as a whole, rather than thinking of it as something specific to this proposal.

>> 4. Being able to declare conformance only to the protocol in a class, and have the required inheritance be implied.
> Sorry, I don't understand that.

That simply means that, instead of saying this:

	class MyActivityViewController: UIViewController, ActivityViewControlling {

It'd be nice if you could say this:

	class MyActivityViewController: ActivityViewControlling {

And have the inheritance from UIViewController be implied.

>> 5. Being able to add factory initializers to the protocol which could initialize any conforming type.
> That would be an important requirement as soon as protocols can contain properties.

Factory initializers are actually not related to properties. They're more about encapsulating the selection of a concrete implementation—basically, they're for patterns like class clusters.

	extension ActivityViewControlling {
		factory init(userInterfaceIdiom: UIUserInterfaceIdiom) {
			switch userInterfaceIdiom {
			case .Phone:
				self = PhoneActivityViewController()
			case .TV:
				self = TVActivityViewController()
				self = PadActivityViewController()

There will probably need to be a way to initialize stored properties—particularly private ones—added by extensions, but this is also necessary for allowing stored properties in concrete type extensions, so we can reuse whatever mechanism we end up with there.

> I'm missing an important feature in your list: being able to declare protocol members with access scopes different from that of the protocol, i.e. private or internal in a public protocol.

Extension methods can always have different access control scopes from the protocol as a whole. The required/abstract methods are a different story, but I'll get into that later.

> Another important feature quite related to that is a "protected" scope.

Let's keep the simultaneously open cans of worms to a minimum.

>> * No problems with private abstract members of public types; all protocol requirements are already as public as the protocol itself.
> That is a marked disadvantage which prohibits defining partial behavior which is filled in by concrete types!
> If protocols should remain like that then that alone is a decisive argument for adding abstract classes.

This is actually an issue with both approaches. You cannot conform to a protocol/inherit from an abstract class unless you fulfill all of the requirements/implement all of the abstract members. This implies that all of those required/abstract members must be visible to any code which can conform/inherit.

In fact, this situation is actually slightly *better* for protocols, because you can always make a protocol more private than its concrete conforming types; the relationship between those types just won't be visible. A subclass, on the other hand, cannot be more visible than its superclass, so you have to make abstract superclasses and their abstract members fully visible.

This issue can be solved by allowing you to make a protocol/class more visible than the ability to conform to/inherit from it (basically, the "sealed" proposal that has come out of the resilience work); then you could give each requirement/abstract member a visibility anywhere between the ability to conform and the ability to see the type.

But in any case, this is an issue for both constructs; I don't think it should cause us to prefer one of them over the other.


Actually, that reminds me that there's another, more procedural, reason we should reject SE-0026: The proposal is woefully incomplete, more of a sketch than a detailed plan ready to be implemented.

Technical gaps:

1. The aforementioned visibility concern. Can abstract members be less visible than the type they belong to? If so, what does that mean for the visibility of abstract classes and their subclasses?
2. Who is responsible for initializing an abstract stored property: the parent class or the child? Only the child knows it's stored, but the parent might need a particular initial value. Perhaps it's the child's responsibility unless the parent explicitly says it will initialize the value? How would it say so?
3. How does abstractness interact with resiliency? Can abstract members become non-abstract? Can an abstract class become concrete?
4. Can you have abstract `let`s? How about `subscript`s? `init`s? A `deinit`? Class members? Any other kinds of members I might have forgotten?
5. Can you have partially-abstract properties? For instance, can you have a property with a concrete getter and an abstract setter? Or concrete accessors but abstract observers?
6. Can abstract classes have associated types? That would be a powerful feature, but it would open a whole 'nother can of worms.
7. If you can have abstract inits, can you also have concrete inits which initialize the class's concrete stored properties? What are the inheritance rules for them?
8. Can you nest types inside of abstract classes? Can those types themselves be abstract? If so, would concrete classes have to implement those types? What if you want them to be value types?

Proposal justification gaps:

9. Why is `override` used when you're implementing abstract members? They're not overriding anything.
10. What's the purpose of abstract `willSet`/`didSet`? 
11. As several other reviews have lamented, there are no particularly good use cases in the proposal.
12. There is no in-depth exploration of alternative approaches. (This would be much easier to do if we had use cases; then we could look at how alternatives would fare.)

I really think that this review has only two reasonable outcomes: reject because we don't want the feature, or reject because the proposal still needs more work. SE-0026 is just not ready for prime time.

Brent Royal-Gordon

More information about the swift-evolution mailing list