The question is what do abstract classes buy you in Swift that you don’t already have from protocols. Protocols can supply function implementation, and can require that only classes implement them.
So how important is declaring storage for properties?
The only thing that I have wanted sometimes is the ability to call super on a protocol override of a default implementation. Abstract classes do have that.
I’m also hoping for that. In C++, we can use SuperclassName::methodName to call the default implementation. But in Swift, protocol is not like superclass whose methods can be overridden. In fact, the following code is not allowed.
protocol P {
func f()
}
extension P {
func f() {}
}
class C: P {}
class D: C {
// this `override` will cause a compilation error
override func f() {}
}
But if you provide the implementation of f in C, it will be fine to override.
Imagine such case where you have lots of properties to initialize in many classes/structs. if we have abstract class, the code may look like this:
abstract class C {
let x: Int
let y: Int
let z: Int
init(x: Int, y: Int, z: Int) {
self.x = x
self.y = y
self.z = z
}
abstract func abstractMethod()
}
class D: C {
let d: Int
init(x: Int, y: Int, z: Int, d: Int) {
self.d = d
super.init(x: x, y: y, z: z)
}
override func abstractMethod() {}
}
class E: C {
let e: Int
init(x: Int, y: Int, z: Int, e: Int) {
self.e = e
super.init(x: x, y: y, z: z)
}
override func abstractMethod() {}
}
If I use protocol, is there any way to avoid the duplicate code of self.xxx = xxx?
@Josh_Osborne Let me give you an example. How would I implement a decorator pattern the way it is done in GoF book? An abstract class allows you to have a shared storage, a default method implementation and, at the same time, it disallows to instantiate itself, so sub-classes would be able to provide their own implementation, call super and initialize the shared storage.
The only thing that a protocol-based solution doesn’t offer is the storage. That’s something I always wanted, but without having to limit myself to classes. I don’t have a solution, unfortunately.
Thus far all the stated shortfalls of protocols vs abstract remain true. There's some movement to add support for a few of them, but whether those will be fully developed and accepted remains to be seen. I still come across instances where an abstract class would feel better than a spider web of protocols, especially in terms of access semantics.
It's been 3+ years now and Swift's use of protocols has been well established. If the core team now (or soon) has the bandwidth to consider it, (also, is there an implementation anywhere?), I would be in favor of the proposal being re-introduced to review.
At first, I was very excited about the possibility of having abstract classes to aid in creating more robust APIs, but, as the deferral note suggests, investing in protocols might be a better idea and I can’t help but agree with that.
I’d be more in favor of focusing on implementing generalized existentials to support such things as existentials of PATs, stored properties in protocols, calling the equivalent of super , and so on.
In case of stored properties in extensions, I’ve had an idea a while ago, but never got around to voicing it. Long story short, it involves allowing stored properties in extensions on non- @frozenstruct s and classes from the same module (so that the final layout and size of the extended type will be known and extension-stored-properties are statically “inserted” into extended types by the time the module is compiled) as well as @objc classes from any module (using objc_getAssociatedObject and objc_setAssociatedObject .
I generally agree. Protocols have solved the vast majority of my use cases. I'm also concerned that adding too many things to protocols will increase the complexity too much.
I wonder if there's some sort of in-between solution that could be introduced. Maybe something like "abstract type" or "type protocol" with most/all the features of an abstract type, but that structs/enums/etc would also be able to adopt. Rather than trying to shoehorn in storage/access/super into protocol, they can be added to that new type.
Protocols don’t have to change in order for this to happen, though. If we get stored properties in extensions, then an abstract base class simply becomes a class-bound protocol, which has stored properties in its extensions. Although, stored properties in protocol extensions would be a lot trickier to pull off. Generally, my point is: in my opinion, a huge chunk of what people have wanted be able to get done in Swift could be enabled by having stored properties added to types through extensions.
This is a big issue. You're not simply adding storage in an extension, you're adding storage to a protocol, which currently has none. My understanding is it's not a matter of extending an existing capability, but adding a whole litany of things under the hood which protocols don't currently have.
We'll also need the ability to easily call the extension's implementation, and add access modifiers to protocols in order to satisfy the things desired from an abstract class.
My point is, that's 3 things just to get the basics of an abstract class, and there are also several other often requested features of protocols.
The more things that are wanted to be added to protocols, the more I tend to think the "right" solution is a separate type, rather than bloating protocols.
This is actually something I had thought about today.
Basically, the nice thing about protocols is that you can multiple inherit them and they can be applied to either a class or struct equally, and even specify what they're allowed to be applied to. The downside is that currently you can't override them if they provide an implementation.
Really not having true multiple inheritance sucks in a lot of cases, because you end up making hundreds of protocols and end up in factoryfactoryfactory land. However there are only two issues that need to be dealt with for multiple inheritance to be a usable language feature:
You cannot allow redundant inheritance. In other words, if you have:
A
B: A
C: A
D: B, C
then D cannot have a seperate B: A and C: A part to it, because then it no longer conforms to A's interface and cannot be used in place of A as inheritance rules enforce. C++ does allow for redundant inheritance but nobody ever accused it of being a nice language to work with.
You have to specify what you're inheriting from when there are conflicts. In the above example that means for any case where B or C overrides A, D must either choose an implementation from B or C or provide its own override. This also means that you can't just call super because there's more than one super which you could be referring to. In C++ you're just forced to write ClassName::methodName() for everything, but a more principled implementation could eliminate the need for that except where you have ambiguity.
I do like the idea of protocols which can have properties and be overridden and also be inherited by both classes and structs, or one or the other selectively. That's some crazy flexibility that I never even thought of.
Being able to eliminate redundant 'extension' declarations would also be nice. The only reason we even need them now is because of the lack of an 'abstract' declaration so the compiler can tell the difference between an interface and an implementation.
I've often wondered if instead of Abstract classes (notably... only useful for class types, and requiring a tightly-related class hierarchy) or bloating the features of Protocols directly, if we could do something even better by enhancing the composition story in Swift.
For example, if we had some kind of first-class composition-through-forwarding mechanism, we might be able to do something like:
public protocol A {
var i: Int { get set }
var foo: String { get }
}
internal struct SomeA: A {
var i: Int
var foo: String {
return "\(i)"
}
}
public struct Composition: A {
@conformance(A) // this type's conformance to `A` is satisfied by this property
internal var a = SomeA()
}
var composed = Composition()
composed.i = 123
print(composed.foo) // -> "123"
If we use our imagination, with a mechanism like this we could solve many of the same kinds of problems that abstract classes solve.
The "shared storage" afforded by abstract classes is achieved here through composition of an internal type which provides storage and implementation of a whole set of semantic capabilities described by the protocol A that can be reused by other A-conforming types, and the PAT A stands in for the abstract class in the type system.
Something like this would also solve other kinds of problems that abstract classes don't help with.
Obviously, this is achievable without any additions to the language, but with a fair bit of boilerplate. One could imagine that such boilerplate rapidly becomes unwieldy and unmanageable for non-trivial example of conformances & type families (and having attempted to utilize such a pattern in a real-world implementation, I can confirm that this is true... it's not something I'd choose to do again without the aid of some sugar).
I like a lot this kind of conformance proxying, but I would also like to have abstract classes in Swift. Being more specific, I have a bunch of base viewcontrollers that require subclasses to implement some specific methods that they depend on internally, but I can’t enforce that at compile time and I end up adding a lot of fatalError like methods that crash at runtime if those required methods are missing
Could you be more specific? What about the idea do you dislike, and why? Is it an ergonomic problem that can be solved with better syntax, or is the concept itself somehow ineffective, unintuitive, impossible to implement, or otherwise flawed? What's an alternative approach that solves the problem?
I agree that the idea as initially presented is not production-ready, but it looks to me like a step towards solving some important problems. In order to reach those solutions the idea must be refined by in-depth discussion and debate, and a subjective, insubstantial dismissal does not serve to encourage that thorough discussion.
I’d like to add that a similar idea was presented during the pitch/review of the Identifiable protocol.
The protocol proposed an id attribute and many people had concerns about hijacking such a common variable name with very specific semantics that there was no way they could guarantee were met by all types that used an id property at the time. It was suggested that a property wrapper @implements could be used so that developers not able to or not wanting to refactor large codebases to meet the new protocols semantic requirements could still adopt the new protocol anyways, albeit by using a different property than id. In that thread the idea was widely accepted as very beneficial and potentially necessary in the future as more protocols are added to the stdlib.