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


(Thorsten Seitz) #1

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

  1. 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()’

Yes and no. That is the general multiple inheritance problem and should be solved somewhere in Swift’s future (probably by adding renaming abilities like Eiffel does, as that seems to be the only solution, at least AFAIK and I think it is a good one). But…

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

…actually it is related to the point above except if you would restrict using override and super to methods inherited from a class and not a protocol (which would be a solution, because then there would only be one candidate, or at least one most specific one, due to single inheritance along the class path).

protocol ActivityViewControlling: UIViewController {

}
extension ActivityViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)

}
}

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.

As I already said above I, too, think that this should be solved in general (I already wrote about a solution for that, i.e. the solution used by Eiffel, in another thread some time ago).

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

Ah, that’s what you meant!

That seems quite natural to me. Anything else would have me surprised :slight_smile:

  1. 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()
              default:
                            self = PadActivityViewController()
        }
  }

Ah, thanks for clearing that up! Yes, that would be useful for implementing class clusters.

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.

Yes, that’s what I thought of when I read “initializer” (my mind obviously had skipped the word “factory”, sorry for that).

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.

Extension methods which have not been declared in the protocol are not part of the type and therefore to be used with caution IMO. Furthermore this won’t work exactly because I have to implement the required method in the extension. It is simply not possible to write a template method with protocols and extensions.

Another important feature quite related to that is a “protected” scope.

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

:slight_smile:

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

Correct.

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

Really? I’ll have to check when I’m at home, because I think I already tried to do this and it was not allowed.

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.

That is not the point. The point is being able to define abstract methods with less visibility, i.e.

public abstract class Foo {

  public func templateMethod() {

        doSomethingFirst()

        doSomethingCustomizable()

        doSomethingLast()

  }

  private func doSomethingFirst() { ... }
  private func doSomethingLast() { ... }

internal func doSomethingCustomizable() { … }

}

Admittedly, “internal” or “private” will often not be the best choice, that’s where “protected” comes into play.

But the important point is that protocols cannot do this now. I would be happy if they could in the future.

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.

Agreed.


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?

As I already said, that’s an important feature IMO. As a consequence abstract classes just have to be as visible as their most visible member. This would seem quite natural to me and not problematic at all.

  1. 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?

I see no problem here. The parent obviously can initialize the property if it wishes to do so (and a setter is declared). If there is no setter the parent obviously doesn’t care about its initial value. That’s just a normal design issue, no language issue.

  1. How does abstractness interact with resiliency? Can abstract members become non-abstract? Can an abstract class become concrete?

I would expect that from a developer point of view but cannot say anything about the technical problems which might play a role here.

  1. Can you have abstract lets? How about subscripts? inits? A deinit? Class members? Any other kinds of members I might have forgotten?

I would expect so. Why not? It is just a typing issue IMO, i.e. declaring an abstract member adds it to the type and makes calling it available from a typing perspective.

  1. 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?

That’s an interesting issue and should probably be part of the behavior discussion. As a matter of fact I already suggested in that discussion to use abstract members in place of the proposed “accessor” construct (and make use of the existing “final” to cover the other variants). This would tie in nicely with abstract classes.

  1. Can abstract classes have associated types? That would be a powerful feature, but it would open a whole 'nother can of worms.

No, why should they? Associated types are the way generics are implemented for protocols. Classes use type parameters. I don’t see why abstract classes would require a change here. To restate: abstract members are just extending the type which is implicitly defined by a class. They are not protocols.

  1. 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?

I don’t see why this should differ from normal inits.

  1. 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?

Yes. Probably. No (IIRC nesting types is just a namespacing construct in Swift and doesn’t have more advanced capabilities like Scala or Beta). If you want them to be value types you have just the same “problem” as with a non abstract class where you would like to derive a value type from. You can’t. I can’t see how this is a new problem only related with abstract classes.

Proposal justification gaps:

  1. Why is override used when you’re implementing abstract members? They’re not overriding anything.

That should be handled similar to implementing members defined by protocols, i.e. no override if none is used there.

  1. What’s the purpose of abstract willSet/didSet?

That should be part of the behavior proposal. See my comments above relating to that.

  1. As several other reviews have lamented, there are no particularly good use cases in the proposal.

That’s unfortunately true.

  1. 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.)

Your are right.

-Thorsten

···

Am 01. März 2016 um 11:30 schrieb Brent Royal-Gordon brent@architechies.com:

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
Architechies