[Draft] Allow declaration of abstract functions and properties on classes


(Thorsten Seitz) #1

Unfortunately, the protocol does not cover all of the cases where a developer might want to specify an interface to be implemented by another entity.

For example, consider the class, which allows the creation of an inheritance hierarchy. Often, a class in a hierarchy exists merely to provide a common implementation to subclasses. Such classes aren't ever intended to be instantiated directly; only subclasses will be instantiated.

To illustrate the point, imagine a view controller class that:

       • Places an animating UIActivityIndicatorView onscreen
   • Performs some operation to retrieve some text
• Puts the text in a UITextView and places it onscreen
       • Hides the UIActivityIndicatorView
Now imagine you had many cases in your application where you could benefit from such a view controller, and each case differed only in the operation required to retrieve the text (represented by Step 2 above).

Ideally, you would be able to achieve this by declaring the interface for a function without needing to specify an implementation, the same way you would with a protocol:

func retrieveText() -> String
In other languages, such as C++, this concept exists in the form of an abstract class. However, Swift does not support this, so developers are forced to provide useless implementations such as:

func retrieveText() -> String

{
fatalError(
"Subclasses must implement retrieveText()"
)
}

The idea here is that subclasses should always provide a retrieveText() implementation, and therefore the call to fatalError() should never be hit.

This has a few significant downsides:

• It forces the developer to write code that should never be executed under normal conditions. This seems like a waste.

     • Because a default implementation is provided--the one that calls fatalError()--the compiler has no way of knowing that the subclasses are supposed to provide an implementation, too.

     • If a subclass implementor forgets to provide a retrieveText() function, the error will not be caught until runtime, and not until a user navigates to the affected portion of the application. This may not occur until the application has shipped.

That's one alternative, yes. Others include:

1. Having a delegate provide the `retrieveText()` method.

2. Having a closure property implement the `retrieveText()` method.

Sorry, but I fail to understand your argument here. Both of these require implementing the abstract method and thereby filling in the concrete behavior, e.g.

func retrieveText() -> String {
        return delegate.retrieveTextFor(self) // or whatever
}

Or do you mean, that you would already require a delegate when defining the base class?

3. Declaring a protocol `ActivityViewControlling` that requires `retrieveText()` and adding the other logic in an `extension ActivityViewControlling where Self: UIViewController`.

I think that 1 or 2 are usually the best way to handle something like this,

I disagree. While delegates (and a closure property is just a delegate as well) are great for dynamically providing a specific behavior (Strategy Pattern) I don’t think using a delegate is the right thing to do for statically providing the behavior. That’s what abstract classes and abstract methods/properties are for.

Example:
When writing a generic graph traversal of a certain kind I can abstract the collection to be used for vertices that have been discovered and wait to be visited. Using a queue I get breadth first traversal while using a stack I get depth first traversal. Modeling that collection as abstract property I can then subclass both kinds of traversal easily fixing the type of the collection statically. It would not make sense to plugin a collection dynamically IMO as I want to have strongly typed BFSTraversal and DFSTraversal implementations in my model.

but let's explore 3 for a minute, because that's a place where Swift could probably be improved.

Currently, Swift allows you to constrain a protocol to only class types:

       protocol ActivityViewControlling: class {
         func retrieveText() -> String
       }
     extension ActivityViewControlling where Self: UIViewController {
           ...
}
class MyActivityViewController: UIViewController, ActivityViewControlling {
         func retrieveText() -> String { ... }
       }

But when you do that, Swift permits you to use any class type, which is a bit weird semantically—ActivityViewControlling can be applied to any class, but it's really only meant to be applied to subclasses of UIViewController. An ActivityViewControlling type which isn't a view controller is kind of meaningless.

       // Why can I do this?
     class PossibleButUseless: ActivityViewControlling {
         func retrieveText() -> String { ... }
       }

Suppose instead we allow a protocol to require that the conforming class inherit from another class. We could then omit the `where` clause and possibly even make the inheritance itself implicit in conforming to the protocol:

     protocol ActivityViewControlling: UIViewController {
           func retrieveText() -> String
       }
     extension ActivityViewControlling {
         ...
}
class MyActivityViewController: ActivityViewControlling {
     func retrieveText() -> String { ... }
       }

This would also relieve some of the pressure on us to support class-plus-protocol typed variables. In many of these cases, *all* types conforming to the protocol should be subclasses of a particular class, but there's no way to express that in the type system. Subclass-only protocols would correct that shortcoming.

Subclass-only protocols would cover most, if not all, of the use cases of abstract classes, but I think they would be a less dramatic change to Swift, because protocols are already types which can't be instantiated and impose requirements on their subtypes. I therefore believe they would be a better, more Swift-like approach to the problem abstract classes are trying to solve.

I am wondering: on one hand you try to push all type information into protocols but on the other hand you want protocols to be able to derive from the implicit type (or protocol) defined by a class.

Extending your line of thought I’ve been thinking whether classes maybe should only represent implementation hierarchies, defining only „protected“ type, whereas public types can only be defined by protocols? This would allow separating both namespaces and solve the problem of having protocols named „Foo“ and classes named „FooImpl“.

-Thorsten

···

Am 25. Februar 2016 um 02:27 schrieb Brent Royal-Gordon <brent@architechies.com>:

--
Brent Royal-Gordon
Architechies


(Daniel Duan) #2

Thorsten Seitz via swift-evolution <swift-evolution@...> writes:

Modeling that collection as abstract property
I can then subclass both kinds of traversal easily fixing the type of the
collection statically.

This "collection" seems like a case where a protocol serves equally well.