Just hit today the lack of abstract classes myself. I'd would be quite happy with a workaround similar to what topic starter wrote, but I also need to call abstract method from concrete method.
Ok, we could do (self as! AbstractMethodRequirementsForC).abstractMethod()
. That's a bit ugly, but works.
Except that is does not because, my abstract base class is also generic. And this means that my protocol gets associated types, and cannot be use as a type anymore.
In the end I was able to push some of the code outside of the class, got rid of the calling abstract method from concrete method, and kept generic base class and a protocol with associated types. This forces all the code that uses this class to be generic, rather then working with existential types, but for my case I was lucky enough to have an existing generic class that I could use to stop propagating generics though the codebase.
But what if we could make it actually work?
Step 1. Enable any protocol to be used as type - see some discussion in Improving the UI of generics
Step 2. Enable generic type aliases with constraints, also mentioned there.
Step 3. And the final bit, that's a new suggestion from me - where Self: AbstractMethodRequirementsForC
So it would look like:
// Having two definitions requires more code, but in a way it makes code cleaner, but separating public interface from implementation.
// Even in languages with built-in support for abstract classes, it is still a good practise to have a separate protocol
protocol CProtocol {
associatedtype U: SomeConstraint
func abstractMethod(_: U) -> U
}
// That's a 100% boilerplate code that should be eliminated somehow
typealias C<T> = CProtocol where U == T
// Typically any implementation of C fits your needs, even if it does not inherit implementation from AbstractC
func use<T>(x: C<T>) {}
// ... but not always
func useAndRequireImplementation<T>(x: AbstractC<T>) {}
class AbstractC<T: SomeConstraint> where Self: C<T> {
var x: T
init(_ x: T) {
self.x = x
}
func concreteMethod() {
// the `where` clause above enables calling methods from AbstractMethodRequirementsForC even though it is not implemented here
self.x = self.abstractMethod(self.x)
}
}
// Error: BarDerived cannot inherit from AbstractC because it does not conform to C<String>
class BadDerived: AbstractC<String> {}
// OK: forward protocol requirements
class AbstractDerived<T>: AbstractC<Array<T>> where Self: C<Array<T>> {}
// OK: protocol requirement satisfied
class ConcrecteC: AbstractC <String>, C<T> {
func abstractMethod(_ x: String) -> String {
return x + x
}
// override works
override func concreteMethod() -> String {
// super works
let s = super.concreteMethod()
return "\"\(s)\""
}
}
use(ConcreteC())
Regarding the problems described in deferral rationale:
An abstract class cannot be instanciated.
This can be prevented in cases when
AbstractClass(...)
is statically spelled, but the behavior of abstract class metatypes must be specified. Islet classObject: AbstractClass.Type = AbstractClass.self
allowed? Doeslet classObject = _typeByName("Module.AbstractClass")
work? If not, then the abstract class object cannot be used for metaprogramming or generic argument binding purposes. If it can be, then there's a static safety hole, sinceclassObject.init(requiredInitializer:)
would have to dynamically fail ifclassObject
refers to an abstract class metatype.
For the let classObject: AbstractClass.Type = AbstractClass.self
, I think it should work, but you should not be able to instantiate anything using that metatype. To instantiate something, you need a metatype of type (AbstractClass & Protocol).Type
. Base.self
does not type check against that type, but ConcreteSubclass.self
does.
Similarly, _typeByName("Module.AbstractClass")
would work and would return Module.AbstractClass.self
, but trying to cast that to the (Module.AbstractClass & Module. Protocol).Type
would fail.