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

Brent Royal-Gordon brent at architechies.com
Fri Mar 4 02:45:16 CST 2016


>> Why is it so important that this feature be expressed strictly within the class hierarchy? 
> 
> Because it's a widely-understood concept within programming that I'd venture most Swift developers already know.

Okay, so it's similar to features in other languages. That *is* a point in its favor, but all the same, Swift doesn't hesitate to ignore the conventional solution when it has a better idea—especially as it pertains to the type system.

> Pretty much everything related to protocols and static dispatch would need to be reconsidered in a proposal: for example, what's the order of execution when you have multiple viewWillAppear() overrides in several protocol extensions?

I assume you mean in extensions to several protocols, all of which are conformed to by a single class?

Okay, let's first talk about the simple case with one protocol, just to get everyone on the same page. Suppose I have these types:

	class Foo { func foo() {} }
	
	protocol P: Foo {}
	extension P { override func foo() { super.foo() } }
	
	class Bar: Foo, P { override func foo() { super.foo() } }
	
	class Baz: Bar { override func foo() { super.foo() } }

I figure if you call `Baz.foo()`, it will call `Bar.foo()`, which calls `P.foo()`, which calls `Foo.foo()`. In other words, the protocol's methods are added "between" the conforming class and its superclass.

(As a digression, if the situation looked like this:

	class Foo { func foo() {} }
	
	class Bar: Foo { override func foo() { super.foo() } }
	
	protocol P: Foo {}
	extension P { override func foo() { super.foo() } }
	
	class Baz: Bar, P { override func foo() { super.foo() } }

The order would be `Baz.foo()`, `P.foo()`, `Bar.foo()`, `Foo.foo()`. Note that this is true even though `P` says you have to inherit from `Foo`: an override may end up overriding a subclass's implementation.)

Now, let's add conflicting conformances:

	protocol P1: Foo {}
	extension P1 { override func foo() { super.foo() } }
	
	protocol P2: Foo {}
	extension P2 { override func foo() { super.foo() } }
	
	class Bar: Foo, P1, P2 { override func foo() { super.foo() } }

Well, we already have a behavior when you do that: the Swift compiler complains at the site of the ambiguous call (in this case, the `super.foo()` in `Bar.foo()`) and to resolve it, you have to cast to the type of the protocol you want. We could do something similar:

	class Bar: Foo, P1, P2 { override func foo() { (super as P1).foo() } }

What if `Bar` doesn't override `foo()`? Currently, we wait until we see a conflicting call site, but honestly I think we should just require an override so that the conforming type's author can sort things out. (Actually, I think we should probably do that all the time. All of this strangeness ultimately flows from the weird hybrid status of protocol extension methods.)

An alternative would be to make it an error to conform to two protocols which override the same method. It will already have to be the case that you can't conform to two protocols which require you to inherit from different classes (unless one is a subclass of the other), so this would simply extend the scope of the existing "conflict prevention".

> Such a proposal will require a lot thoughtful, careful design.

You are absolutely right about this. But a lot of the odd corners here are things which already confuse people and probably ought to be revisited anyway.

> Meanwhile, abstract classes are well-known concept with a well-understood design and well-defined behavior.

Yes, and their failings are well-documented as well. Remember what Sean Heber said in his review:

> I’ve mentioned wanting this myself on this list and have run into situations where I *really* wished I had it at the time (instead of having to do fatalError()), but ultimately I’ve almost always gone back to redesign those things to get rid of the abstract base class pattern and improved the design in the process. Perhaps this is an anti-pattern and Swift should not encourage it. I’m not sure.


We all know what we're getting with abstract classes—and so we know that it has significant flaws and limitations. I think we'd be better served trying something else, something that has a shot at improving on the state of the art.

(To be honest, I also think it's a good thing that a protocol with a class requirement will probably be pretty easy to refactor into a class with a delegate protocol.)

>> protocol ActivityViewControlling: UIViewController
> 
> Did you just declare a protocol ActivityViewControlling that extends another protocol called UIViewController? Or did you somehow restrict the ActivityViewControlling protocol to only being used with the UIViewController class? What is being stated in your notation is not obvious to me at first glance.

Given that class declarations have the same problem—is the first type listed a superclass or a protocol?—I think that particular ship has sailed.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list