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

Austin Zheng austinzheng at gmail.com
Thu Mar 3 17:22:45 CST 2016


Hi Brent,

I read through your proposal (skimmed it when it was posted, and then read
through it closely when you brought it up again). It's a really interesting
proposal and would be a strong addition to Swift, and I really think it
deserves to be proposed as a formal alternative to abstract classes. I
imagine it would garner considerably more support than has been shown in
this thread (which has been more about the philosophy of POP than concrete
solutions), and probably stands a decent chance of being accepted over this
proposal (if either is accepted at all).

That being said, here are my personal thoughts and/or objections. (I don't
hold any of these dearly, and would love to be convinced otherwise. I am
all too aware that today's "this is too strange" language concepts may
become tomorrow's indispensable tools :-)

First of all: I'm wary about making protocols even more conceptually
complicated than they already are. The term 'protocol' already refers to
two distinct concepts - protocols containing dynamically dispatched method
requirements, like the Objective-C equivalent, and protocols with
associated types that can only be used as constraints on generics. This is
a perennial source of confusion (c.f. multiple blog posts on trying and
failing to compare two 'Equatable' objects). Now we have default
implementations for protocol methods, but those are statically dispatched
and so the rules of whether the default implementation or a type's specific
implementation are called have repeatedly surprised people (the IBM 'Swift
pitfalls' article is a good example, as are the occasional proposals to
change this behavior).

I see the orthogonality of abstract classes to protocols as a desirable
thing: abstract classes/methods are a simple concept intended for a
specific use case and (in my opinion) well-suited towards fulfilling that
use case.

My second concern involves the ramifications of adding dynamic dispatch to
something like these augmented protocols (or multiple inheritance of
implementation in general, e.g. with mixins). How are calls to 'super', or
usage of an implementation defined in a supertype resolved? In the simple
case:

protocol P : C {
  override func foo() { super.foo() }
}

class C {
  func foo() { ... }
}

class C1 : C, P {
  override func foo() { super.foo() }
}

If C1().foo() is called, what is the call chain? C1's foo(), then P's, then
C's? What if you have multiple protocols that sit on the same level?

protocol Pa : C1 {
  override func foo() { super.foo() }
}
protocol Pb : C1 {
  override func foo() { super.foo() }
}

class C2 : C1, Pa, Pb {
  override func foo() { super.foo() }
}

At some point we'd have to deal with method resolution order a la multiple
inheritance, and I think whether having multiple inheritance and requiring
developers to learn how Swift linearizes method calls is a good thing is
worth a major discussion itself. Another option would be to restrict a
class to conform to at most one protocol with an inheritance requirement,
but that would remove one of the most compelling advantages of the
protocol-based solution - the ability for a class to fulfill multiple
abstract type requirements.

(Default protocol implementations aren't subject to this problem; if you
try to create a situation in which there would be an ambiguity as to which
impl was called the compiler will complain, and there is no concept of
'super' there.)

Again, I see the limitation to single inheritance of abstract classes in
this regard as a virtue. Most of the things I want to model using abstract
classes are most clearly expressed in terms of a single inheritance of
implementation class hierarchy, in which responsibility for implementation
is neatly split into two categories: requirements implemented at the
abstract class (or above), and requirements implemented below the abstract
class. (This is recursive; responsibility for requirements in the second
category can again be split in two by further abstract classes, although
the usual deep class hierarchy caveats apply.)

Anyways, I hope this helps explain my reasoning wrt abstract classes vs
protocols. At the very least, I hope it allows you to sharpen your proposal
and make it even better.

Best,
Austin


On Thu, Mar 3, 2016 at 2:16 PM, Brent Royal-Gordon via swift-evolution <
swift-evolution at swift.org> wrote:

> > Yes, a protocol will force you to provide an implementation. However, a
> protocol *by itself* cannot force the implementor of a class to provide an
> implementation unless that class declares itself in conformance with that
> protocol.
> >
> > ONLY an abstract class, ON ITS OWN, can force concrete subclassers to
> provide an implementation for a given thing.
> >
> > Protocols can't do this, because if you forget to declare conformance
> with the protocol, the compiler can't enforce anything. This pushes what
> could be caught at compile-time to a runtime (potentially crashing) problem.
>
> If you forget to declare inheritance from an abstract class, it won't
> force you to implement those methods, either.
>
> I do completely understand your concerns about the class + protocol
> workaround which is currently necessary here. That's why I'm proposing we
> allow protocols to require inheritance, override members, create stored
> properties, etc. That would allow the protocol to *wholly* replace the base
> class, taking on the job of providing both the concrete portions of the
> implementation and the requirements subtypes need to satisfy.
>
> > Abstract classes enforce a requirement that a given portion of the class
> hierarchy provide an implementation of X.
>
>
> Protocols don't operate entirely within the class hierarchy, but Swift's
> type system is broader than just a class hierarchy. In the context of the
> entire type (directed acyclic) graph, they play the same role of enforcing
> requirements on subtypes. And with the proper features in place, that's all
> we really need.
>
> > Swift isn't ONLY about protocol oriented programming. Classes are here
> for a reason, and they should not be relegated to second-class status where
> people refuse to consider class-only functionality just because the concept
> can't be shoehorned into something protocol-related.
>
> For what it's worth, even though I prefer the protocol solution in this
> case, I share your frustration with the reviews that do little more than
> recite "Protocol-Oriented Programming" as a slogan. I believe there are
> strong, convincing reasons to prefer the protocol approach here, but none
> of them are captured by that phrase.
>
> But I think the pro-abstract class side is suffering from the same
> problem. Why is it so important that this feature be expressed strictly
> within the class hierarchy? Both extending classes to support abstract
> class-style functionality *and* extending protocols to support abstract
> class-style functionality bring abstract class-style functionality into the
> language. I'm not saying they're completely interchangeable and
> equivalent—there are differences, there are reasons to prefer one over the
> other—but they ultimately provide most of the same functionality.
>
> If we went the abstract class route instead of the protocol route, I would
> be disappointed—I would think that we took the well-trodden path rather
> than explore something that was better and more consistent with the
> language. But I wouldn't feel like there was a gaping void in the language,
> because abstract classes *would* fulfill most of the use cases here.
>
> I don't understand why you and the other abstract class supporters don't
> feel the same way—why you seem to treat this like a fight for your lives,
> not like a fight for the modestly superior design. The only thing that
> makes sense to me is dogmatism, but I want to give you more credit than
> that.
>
> So explain to me: if this code worked:
>
>         protocol ActivityViewControlling: UIViewController {
>                 func retrieveText() -> String
>         }
>         extension ActivityViewControlling {
>                 @IBOutlet var messageLabel: UILabel!
>
>                 override func viewWillAppear(animated: Bool) {
>                         super.viewWillAppear(animated)
>                         messageLabel.text = retrieveText()
>                 }
>         }
>
> What would you feel was missing compared to this?
>
>         abstract class ActivityViewController: UIViewController {
>                 abstract func retrieveText() -> String
>
>                 @IBOutlet var messageLabel: UILabel!
>
>                 override func viewWillAppear(animated: Bool) {
>                         super.viewWillAppear(animated)
>                         messageLabel.text = retrieveText()
>                 }
>         }
>
> --
> Brent Royal-Gordon
> Architechies
>
> _______________________________________________
> 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/20160303/3db4e56b/attachment.html>


More information about the swift-evolution mailing list