[Review] SE-0026 Abstract classes and methods

Without getting into a protracted argument, I just want to reiterate that:

* Foundation and Cocoa remain (and will remain for years) a critical part
of Swift software development.
* These frameworks are built around inheritance hierarchies; what you can
do to retrofit POP onto them is quite limited. If I want a partially
customized UIView that makes changes through the existing API hooks I'm
going to subclass, not create a protocol.
* These frameworks already contain classes and methods that are 'abstract',
where that status is enforced through documentation or runtime asserts.
This is, in my opinion, a hole that should have been filled a long time ago
(but, for various technical reasons, couldn't); we have a chance now to do
exactly that.

In terms of whether or not it's worth making changes to Swift et al 'just'
to improve Objective-C interop...I think the community has pretty much
decisively come down on the side of yes (see: typed selectors, importing of
user-defined Objective-C generics as Swift generics, systematic renaming of
imported Objective-C method names to conform with Swift compatibility...).

Austin

···

On Thu, Mar 3, 2016 at 12:55 PM, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

> On Mar 3, 2016, at 12:42 PM, Evan Maloney <emaloney@gilt.com> wrote:
>
> There has been a proposal floated (by Dave Abrahams if I recall--and if
I'm wrong, sorry!) to change protocols so conformance is NOT inherited by
subclasses by default.
>
> I would just like to point out that if that happens, all of the
arguments against abstract classes go out the window, and we absolutely
WILL need abstract classes, because all the convoluted gymnastics required
to get protocols to kinda-sorta (but not really) provide some of what
abstract classes provide won't be possible.
>
> Long live the class hierarchy!

That was me. I only proposed it as an *option* for the conformer, though,
not as a sweeping change.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Not necessarily commenting for or against abstract classes, but the
following logic confuses me:

"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."

What is the difference between forgetting to declare conformance to a
protocol and forgetting to subclass an abstract class? An abstract class,
ON ITS OWN, cannot force a class to do anything unless that class declares
itself a subclass of the abstract class.

···

On Thu, Mar 3, 2016 at 3:39 PM, Evan Maloney via swift-evolution < swift-evolution@swift.org> wrote:

- What is your evaluation of the proposal?

I could not more strongly support the proposal to add abstract classes,
functions and properties to Swift.

None of the proposals involving protocols as a solution to this have
addressed the fundamental issue:

*ONLY* abstract classes ON THEIR OWN allow the compiler to enforce
providing an implementation for a given function.

For example -- and not picking against the author of this, it's just the
most recent example to grace my inbox:

I am against the proposal because I feel it goes against the Protocol
Oriented Programming patterns found in the Swift Standard Library. Current
protocol functionality can provide a solution for the given example. If a
protocol property is not implemented, the compiler will not let you
compile.

This does not address the fundamental concern of abstract classes.

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.

Abstract classes enforce a requirement that a given portion of the class
hierarchy provide an implementation of X.

• Is the problem being addressed significant enough to warrant a change to
Swift?

Yes.

No, protocols implement this functionality, but with a different syntax.
There is no lacking or additional functionality this proposal provides.

No, protocols *do not implement* this functionality. They provide
something else, and are orthogonal to the class hierarchy.

• Does this proposal fit well with the feel and direction of Swift?

Absolutely. Swift is about safety, about letting the compiler perform
safety checks. ONLY abstract classes allow the compiler to enforce that an
implementor of a given subclass has provided an implementation for a given
function.

Solutions that require the use of protocols can only be checked for
correctness if the implementor of the subclass in question ALSO declares
conformance to the protocol.

In other words, a protocol-based solution can only be checked by the
compiler if the developer does 2 things correctly (declares protocol
conformance and provides an implementation), whereas abstract classes allow
the compiler to perform checks as long as the developer does 1 thing
(subclass a given class).

No. It goes against protocol oriented programming.

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.

Furthermore, this is only for classes, structs are being excluded in this
case. I feel that with most of the standard library being implemented as
structs, this is not acceptable.

Rather than reject this proposal because "this is only for classes,"
perhaps recognize that this is only for classes PRECISELY because this type
of problem--the one that abstract classes addresses--only occurs with a
class hierarchy.

• If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

C++ and have found them EXTREMELY useful. This proposal is in line with
that.

• How much effort did you put into your review? A glance, a quick reading,
or an in-depth study?

I've been following this discussion very closely, and before I realized
there was already a proposal in the queue for abstract classes, I also
wrote a similar proposal:

Allow declaration of abstract functions and properties on Swift classes · GitHub

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Without getting into a protracted argument, I just want to reiterate that:

* Foundation and Cocoa remain (and will remain for years) a critical part of Swift software development.
* These frameworks are built around inheritance hierarchies; what you can do to retrofit POP onto them is quite limited. If I want a partially customized UIView that makes changes through the existing API hooks I'm going to subclass, not create a protocol.
* These frameworks already contain classes and methods that are 'abstract', where that status is enforced through documentation or runtime asserts. This is, in my opinion, a hole that should have been filled a long time ago (but, for various technical reasons, couldn't); we have a chance now to do exactly that.

In terms of whether or not it's worth making changes to Swift et al 'just' to improve Objective-C interop...I think the community has pretty much decisively come down on the side of yes (see: typed selectors, importing of user-defined Objective-C generics as Swift generics, systematic renaming of imported Objective-C method names to conform with Swift compatibility...).

Austin

I couldn't agree more with Austin.

I feel like where getting close to a religious argument where the solution to everything is protocols.

The fact is, classes exist as first-class entities in Swift. Abstract classes solve a need that is only partially solved (and in a much more convoluted fashion) by protocols.

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.

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.

For those of us who support abstract classes, it's quite literally a fight for the life of SE-0026.

That's the proposal we're discussing.

The choice before us now isn't "a solution involving protocols" versus "a solution involving abstract classes", the choice is between abstract classes or nothing.

It's an up-or-down vote on abstract classes.

So you'll have to forgive the adherents for being a bit animated about it. ;)

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()
    }
  }

Per se, I have no objections to your notation. When a specific proposal comes up for a vote, I might support it. So far I am intrigued.

But there's an awful lot that would have to change about Swift protocols in order to get the idea above to work, and that's assuming you could get the community to support it.

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?

Such a proposal will require a lot thoughtful, careful design. Meanwhile, abstract classes are well-known concept with a well-understood design and well-defined behavior.

The abstract class proposal before us now, on the other hand, is much more limited in scope.

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()
    }
  }

The abstract class concept is quite common in computing, and therefore--for a lot of people--just seeing the 'abstract' keyword conveys all that's needed.

Your notation, on the other hand:

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.

Finally, I don't think your idea necessarily obviates the need for abstract classes. There doesn't have to be Just One Way to achieve something in Swift. I don't think the proposal for abstract classes needs to be torpedoed in order to build support for your proposal.

For modeling subtype requirements we would need the ability to declare protocol members with reduced access scope like private or internal and hopefully protected, as subtype requirements most often are not public.

-Thorsten

···

Am 03.03.2016 um 23:16 schrieb Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org>:

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.

If you argue that the type graph should governed by protocols only, then we should think about whether it makes sense to take classes (and structs, enums) out of that completely, i.e. these do not define types and classes just provide implementation inheritance. Protocols could then even have their own namespace solving some naming issues. But I'm note sure whether that's really desirable. If not then IMO classes should have comparable capabilities to protocols with regards to shaping the type graph and not be second class citizens.

-Thorsten

···

Am 03.03.2016 um 23:16 schrieb Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org>:

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.

I also like modern things and I am happy with all the things that are *not*
being included in Swift as well.

I understood what you said, Lord. Sometimes I wonder who are the people
that decides what is “best” and what is “correct” and what is to be
encouraged or not, but I’ll not continue this discussion. We will have to
agree to disagree about some points.

Regards,

-Van

···

On Fri, Feb 26, 2016 at 9:34 PM, Joseph Lord <joseph@lordweb.net> wrote:

On Feb 26, 2016, at 10:54 PM, Vanderlei Martinelli via swift-evolution < > swift-evolution@swift.org> wrote:
>
> We have a hammer and pliers. Now we invented a screwdriver, let's throw
the hammer in the trash. (If it was a sonic screwdriver would be cool.)
>
> I believe that each tool is used for a purpose. Taking the possibility
of other tools just because there is a new way to another does not seem to
make sense.

The path of including everything that is useful leads to C++ and isn't
what I want for Swift.

> Otherwise, what about eliminate classes for good? What do you (plural)
think? So the Swift will be fully POP. (This remember me: "POP goes my
heart!" I can swear I'm listening someone singing this song here.)

If it wasn't for existing code and particularly Cocoa Touch I might be
tempted to propose removing implementation inheritance. Even the if that
was the case a reference type would still be desirable and probably
necessary.

> I like POP anda I like OOP. I'd like to use both in Swift development.
>
> Remembering that Cocoa/CocoaTouch is fundamentally made using classes
and subclasses in mind. I know that Objective-C does not have abstract
classes, but this is a defect, not a quality.

Yes they are very class based, but they are also heavily protocol and
delegation based and I think that is the better pattern to encourage. The
only place I can think of where there is an abstract class is in gesture
recognition and I think it would be better if there was an additional
delegate instead.

> Well... Maybe we have a whole new set of frameworks for OS
X/iOS/watchOS/tvOS coming and I do not know?

Existing code will not disappear even if that does happen but that doesn't
mean inheritance should be encouraged.

Joseph

We have a hammer and pliers. Now we invented a screwdriver, let's throw the hammer in the trash. (If it was a sonic screwdriver would be cool.)

I believe that each tool is used for a purpose. Taking the possibility of other tools just because there is a new way to another does not seem to make sense.

The path of including everything that is useful leads to C++ and isn't what I want for Swift.

Otherwise, what about eliminate classes for good? What do you (plural) think? So the Swift will be fully POP. (This remember me: "POP goes my heart!" I can swear I'm listening someone singing this song here.)

If it wasn't for existing code and particularly Cocoa Touch I might be tempted to propose removing implementation inheritance. Even the if that was the case a reference type would still be desirable and probably necessary.

I like POP anda I like OOP. I'd like to use both in Swift development.

Remembering that Cocoa/CocoaTouch is fundamentally made using classes and subclasses in mind. I know that Objective-C does not have abstract classes, but this is a defect, not a quality.

Yes they are very class based, but they are also heavily protocol and delegation based and I think that is the better pattern to encourage. The only place I can think of where there is an abstract class is in gesture recognition and I think it would be better if there was an additional delegate instead.

Well... Maybe we have a whole new set of frameworks for OS X/iOS/watchOS/tvOS coming and I do not know?

Existing code will not disappear even if that does happen but that doesn't mean inheritance should be encouraged.

Joseph

···

Sent from my phone

On Feb 26, 2016, at 10:54 PM, Vanderlei Martinelli via swift-evolution <swift-evolution@swift.org> wrote:

I really dislike the fact that default methods implemented in a protocol extension are statically dispatched by default... An optimisation which can have confusing side effects should be programmer driven not automagically applied, but we are going off topic I guess ;).

···

Sent from my iPhone

On 1 Mar 2016, at 12:13, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

Le 1 mars 2016 à 11:29, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

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.

Don’t you go too fast? Methods declared in protocol extensions are very different from methods declared inside the protocol: they are not dynamically dispatched.

I, too, am concerned about the flat world of protocols' access control.

Gwendal

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

My thoughts exactly! I don’t not understand the following statement:

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.

My argument is that declaring to be the subclass of an abstract class, or declaring to be in conformance with a protocol would have the same effect. Even the syntax would be the same, e.g.

class ConcreteClass: AbstractSuperclass { … }

or

class ConcreteClass: ProtocolWithoutExtensions { … }

  Coleman,

···

On Mar 3, 2016, at 4:21 PM, Matthew Judge <matthew.judge@gmail.com> wrote:

Not necessarily commenting for or against abstract classes, but the following logic confuses me:

"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."

What is the difference between forgetting to declare conformance to a protocol and forgetting to subclass an abstract class? An abstract class, ON ITS OWN, cannot force a class to do anything unless that class declares itself a subclass of the abstract class.

On Thu, Mar 3, 2016 at 3:39 PM, Evan Maloney via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

- What is your evaluation of the proposal?

I could not more strongly support the proposal to add abstract classes, functions and properties to Swift.

None of the proposals involving protocols as a solution to this have addressed the fundamental issue:

*ONLY* abstract classes ON THEIR OWN allow the compiler to enforce providing an implementation for a given function.

For example -- and not picking against the author of this, it's just the most recent example to grace my inbox:

I am against the proposal because I feel it goes against the Protocol Oriented Programming patterns found in the Swift Standard Library. Current protocol functionality can provide a solution for the given example. If a protocol property is not implemented, the compiler will not let you compile.

This does not address the fundamental concern of abstract classes.

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.

Abstract classes enforce a requirement that a given portion of the class hierarchy provide an implementation of X.

• Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

No, protocols implement this functionality, but with a different syntax. There is no lacking or additional functionality this proposal provides.

No, protocols *do not implement* this functionality. They provide something else, and are orthogonal to the class hierarchy.

• Does this proposal fit well with the feel and direction of Swift?

Absolutely. Swift is about safety, about letting the compiler perform safety checks. ONLY abstract classes allow the compiler to enforce that an implementor of a given subclass has provided an implementation for a given function.

Solutions that require the use of protocols can only be checked for correctness if the implementor of the subclass in question ALSO declares conformance to the protocol.

In other words, a protocol-based solution can only be checked by the compiler if the developer does 2 things correctly (declares protocol conformance and provides an implementation), whereas abstract classes allow the compiler to perform checks as long as the developer does 1 thing (subclass a given class).

No. It goes against protocol oriented programming.

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.

Furthermore, this is only for classes, structs are being excluded in this case. I feel that with most of the standard library being implemented as structs, this is not acceptable.

Rather than reject this proposal because "this is only for classes," perhaps recognize that this is only for classes PRECISELY because this type of problem--the one that abstract classes addresses--only occurs with a class hierarchy.

• If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

C++ and have found them EXTREMELY useful. This proposal is in line with that.

• How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've been following this discussion very closely, and before I realized there was already a proposal in the queue for abstract classes, I also wrote a similar proposal:

Allow declaration of abstract functions and properties on Swift classes · GitHub

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

It's the difference between forgetting to do two things and forgetting to do just one.

Let's say I have a framework that requires me to supply a subclass of Foo to do something useful.

With abstract classes, I just need to subclass Foo, and the compiler tells me what implementations I need to provide.

With the protocol-based "solution" being proposed, I need to subclass Foo *and* I need to remember to declare conformance to FooProtocol. Then and only then can the compiler enforce anything.

2 != 1

···

On Mar 3, 2016, at 4:21 PM, Matthew Judge <matthew.judge@gmail.com> wrote:

Not necessarily commenting for or against abstract classes, but the following logic confuses me:

"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."

What is the difference between forgetting to declare conformance to a protocol and forgetting to subclass an abstract class? An abstract class, ON ITS OWN, cannot force a class to do anything unless that class declares itself a subclass of the abstract class.

For modeling subtype requirements we would need the ability to declare protocol members with reduced access scope like private or internal and hopefully protected, as subtype requirements most often are not public.

I've spoken about this elsewhere, but:

- For both abstract classes and protocols, all required/abstract members need to be visible everywhere you're permitted to conform/inherit.
- There is currently no way in Swift to decouple a protocol's/class's visibility from the ability to conform to/inherit from it, so neither construct can currently offer this feature.
- However, this feature is likely to come at least for classes as part of resiliency. I think it's a good idea for protocols, too.

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.

If you argue that the type graph should governed by protocols only, then we should think about whether it makes sense to take classes (and structs, enums) out of that completely, i.e. these do not define types and classes just provide implementation inheritance. Protocols could then even have their own namespace solving some naming issues. But I'm note sure whether that's really desirable. If not then IMO classes should have comparable capabilities to protocols with regards to shaping the type graph and not be second class citizens.

I don't think that the type graph should be governed entirely by protocols, and that structs, enums, and classes should be second-class citizens at the periphery of a graph of protocols. But I *do* think that this feature does not necessarily need to be implemented in classes.

In many languages, the class hierarchy is all there is, so of course the only way to solve the abstract class use cases is within the class hierarchy. But in Swift, the class hierarchy lives within a larger type graph, and that type graph already includes entities which are quite similar to abstract classes. I simply think that our preferred solution should be to extend the existing feature, rather than duplicating (parts of) it within the class hierarchy.

1. They cannot be instantiated.
2. They impose requirements on their subclasses.
3. They provide a concrete implementation (which can use those requirements).
4. They anchor the class to the class hierarchy.
5. They can participate in inheritance by overriding and being overridden.

Protocols implement #1, #2, and (much of) #3; current Swift classes implement #3, #4, and #5.

But here's the thing: if we enhance protocols with #4 and #5, *all protocols* become more capable. For instance, if we allow protocol extensions to override superclass methods, that could *also* make conditional extensions more powerful.

  extension MyProtocol where Self: SomeClass {
    override func aMethod() {
      // Woohoo!
    }
  }

If we instead add abstract classes to classes...well, that's nice for when you want to write an abstract class, but it doesn't improve anything else.

The protocol route is off the beaten path, and it would take longer to develop. But I think it has significant benefits:

- It reuses an existing construct which already plays a similar role.
- By reusing that existing construct, it automatically gets useful features (like associated types) which there are no plans to provide with abstract classes.
- It enhances those constructs with new features that may be useful even for other use cases.
- It can be built out incrementally.
- When fully built, it will permit things that abstract classes don't (like inheriting from a subclass of the required class, rather than from the required class directly).

Abstract classes are (likely*) faster to add, and they're the more common solution. But if we think the protocol solution will do more good in the long run, I don't think this use case is so urgent that we can't afford to wait for something better.

* I say "possibly" because, as I pointed out in my review, SE-0026 is a very incomplete proposal and there are several significant details which it doesn't specify clearly.

···

From my perspective, abstract classes have five defining traits:

--
Brent Royal-Gordon
Architechies

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

Brent, please consider the sample code below:

  // Framework land
  public abstract class DatabaseRecord {
    // DatabaseRow initializer
    public required init(row: DatabaseRow) {
      referenceRow = row
    }

    // The table name
    public abstract class func databaseTableName() -> String

    // What should be stored in the database
    public abstract var persistedRow: DatabaseRow
    
    // True if record has been changed since last fetch
    public var hasChanges: Bool {
      // return complex computation based on referenceRow and persistedRow
    }
    
    // Hidden implementation detail
    internal var referenceRow: DatabaseRow
  }
  
  // User land
  class Person : DatabaseRecord {
    var name: String
    class func databaseTableName() -> String { return "persons" }
    init(row: DatabaseRow) {
      name = row["name"]
      super.init(row)
    }
    var persistedRow: DatabaseRow { return Row("name": name) }
  }

  let person = Person(row: …)
  person.name // "foo"
  person.hasChanges // false
  person.name = "bar"
  person.hasChanges // true

How do we express this with protocols?

  // Framework land
  public protocol DatabaseRecord {
    var referenceRow: DatabaseRow { get set }
    var persistedRow: DatabaseRow { get }
    static func databaseTableName() -> String
    init(row: DatabaseRow)
  }

  extension DatabaseRecord {
    public var hasChanges: Bool {
      // return complex computation based on referenceRow and persistedRow
    }
  }
  
  // User land
  class Person: DatabaseRecord {
    var referenceRow: DatabaseRow
    var name: String
    init(row: DatabaseRow) {
      name = row["name"]
      super.init(row)
    }
    class func databaseTableName() -> String { return « persons" }
    var persistedRow: DatabaseRow { return Row("name": name) }
  }

  let person = Person(row: …)
  person.name // "foo"
  person.hasChanges // false
  person.name = "bar"
  person.hasChanges // true
  person.referenceRow = … // messed up internal state

The problem with protocols is that the implementation detail referenceRow has to be public (when it should remain an implementation detail), and that the conforming types must provide it (when they should not even know about its existence).

So abstract classes and protocols are NOT on the same stage today regarding members visibility.

Gwendal

···

Le 4 mars 2016 à 08:45, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

For modeling subtype requirements we would need the ability to declare protocol members with reduced access scope like private or internal and hopefully protected, as subtype requirements most often are not public.

I've spoken about this elsewhere, but:

- For both abstract classes and protocols, all required/abstract members need to be visible everywhere you're permitted to conform/inherit.
- There is currently no way in Swift to decouple a protocol's/class's visibility from the ability to conform to/inherit from it, so neither construct can currently offer this feature.
- However, this feature is likely to come at least for classes as part of resiliency. I think it's a good idea for protocols, too.

So that is what it boils down to then. So you believe that this change is necessary so that:

class ContreteClass: SuperClass, AbstractProtocol { … }

becomes

class ContreteClass: AbstractClass { … }

?

It is my humble opinion that if this is the core advantage of this proposal, it should not be accepted. This is a major change in the language, not just a small new feature to be added, and if it can be done with existing functionality (like protocols) then at least it should be postponed for future review for Swift 4. For the sake of discussion lets say this does has its advantages, lets put that in the context of Swift 3 development.

Pros:
- Easier Syntax

Cons:
- Already possible with a little different syntax. Nonetheless, if you use protocols properly the EXACT functionality can be achieved. The compiler WILL force you to write an implementation for a protocol.
- Again, protocols are shared between structs and classes, its a syntax unifier to use protocols. With a language that uses structs for 97% of its Standard Library, protocols seem as a better idea for the sake of clarity and unity. Each new feature has o be learned by all Swift programmers. Why add a different syntax (that has to be learned) for existing functionality, when protocols are shared between value and reference types?
- Will disrupt stabilization of Swift 3, should be postponed for Swift 4 since its a mejor language change

If you could provide an example of functionality this proposal will implement, that cannot be achieved currently in Swift, then I believe it would be worth it to introduce such a major new syntax, only for classes.

  Coleman,

···

On Mar 3, 2016, at 4:26 PM, Evan Maloney <emaloney@gilt.com> wrote:

On Mar 3, 2016, at 4:21 PM, Matthew Judge <matthew.judge@gmail.com> wrote:

Not necessarily commenting for or against abstract classes, but the following logic confuses me:

"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."

What is the difference between forgetting to declare conformance to a protocol and forgetting to subclass an abstract class? An abstract class, ON ITS OWN, cannot force a class to do anything unless that class declares itself a subclass of the abstract class.

It's the difference between forgetting to do two things and forgetting to do just one.

Let's say I have a framework that requires me to supply a subclass of Foo to do something useful.

With abstract classes, I just need to subclass Foo, and the compiler tells me what implementations I need to provide.

With the protocol-based "solution" being proposed, I need to subclass Foo *and* I need to remember to declare conformance to FooProtocol. Then and only then can the compiler enforce anything.

2 != 1

Not really the same : The abstract class support encapsulation : In the abstract class, you can have private attributes and privates methods that encapsulate some kind of behavior and only some methods will be made abstract.

Encapsulation cannot be supported by protocols and if you want to add a full encapsulation in protocol, it will result of a kind of class in protocol syntax….

Regards,

David

···

Le 3 mars 2016 à 22:23, Alsey Miller via swift-evolution <swift-evolution@swift.org> a écrit :

My thoughts exactly! I don’t not understand the following statement:

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.

My argument is that declaring to be the subclass of an abstract class, or declaring to be in conformance with a protocol would have the same effect. Even the syntax would be the same, e.g.

class ConcreteClass: AbstractSuperclass { … }

or

class ConcreteClass: ProtocolWithoutExtensions { … }

  Coleman,

It's the difference between forgetting to do two things and forgetting to do just one.

Let's say I have a framework that requires me to supply a subclass of Foo to do something useful.

With abstract classes, I just need to subclass Foo, and the compiler tells me what implementations I need to provide.

With the protocol-based "solution" being proposed, I need to subclass Foo *and* I need to remember to declare conformance to FooProtocol. Then and only then can the compiler enforce anything.

2 != 1

  In addition, there is a different meaning between protocol and inheritance. When you inherit, you specialize a behavior with some piece of code. For example, consider UIView class. When you want to make a custom class, you inherit UIView and with a custom drawRect. This has no meaning to create a protocol to implement the drawRect because drawRect handle a behavior (specializing UIView) and not a delegate or datasource external interface (i.e protocol).

  * Protocol is the right choice to provide external interface such as datasource, event handler, delegate.
  * Extension is the right behavior for extension.
  * Inheritance is the right choice for specialization.

These 3 needs does not compete each other but are required for a multi-paradigme language that should handle severals kind of architecture designs.

That’s the reason I pull abstract class proposal because in our company we have the 3 kinds of architecture patterns.

That’s not easy to provide a simple and readable example for abstract classes, but there is actually a need. In some design, POP is a workaround for abstract classes. This works, but according to architecture design, it is a workaround.

Regards,

David

Let's say I DON'T have a framework that requires me to supply a subclass of
(class) Foo to do something useful, instead I have a framework that
requires me to have my class conform to (protocol) Foo... my problem with
your "2 things vs 1 thing" argument is that it assumes that the subclassing
has to happen in the first place.

I'm not convinced that using protocols in this way is a better solution
than an abstract class, but as protocols develop the POP argument isn't
necessarily "subclass and conform" it's "conform instead of subclass" - so
1 == 1

I do think that a proper evaluation of abstract classes cannot be done
without examining what Swift would look like with protocols expanded to
fill a similar role (which of the two approaches would add more complexity,
which would be more powerful, etc.)

···

On Thu, Mar 3, 2016 at 4:26 PM, Evan Maloney <emaloney@gilt.com> wrote:

> On Mar 3, 2016, at 4:21 PM, Matthew Judge <matthew.judge@gmail.com> > wrote:
>
> Not necessarily commenting for or against abstract classes, but the
following logic confuses me:
>
> "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."
>
> What is the difference between forgetting to declare conformance to a
protocol and forgetting to subclass an abstract class? An abstract class,
ON ITS OWN, cannot force a class to do anything unless that class declares
itself a subclass of the abstract class.

It's the difference between forgetting to do two things and forgetting to
do just one.

Let's say I have a framework that requires me to supply a subclass of Foo
to do something useful.

With abstract classes, I just need to subclass Foo, and the compiler tells
me what implementations I need to provide.

With the protocol-based "solution" being proposed, I need to subclass Foo
*and* I need to remember to declare conformance to FooProtocol. Then and
only then can the compiler enforce anything.

2 != 1