Abstract class in Swift?

I googled dialectic :slight_smile:: the art of investigating or discussing the truth of opinions. So just to clarify, I am a big fan of purely functional programming, and that kind of makes me also a fan of protocol-oriented programming as protocols are basically just type classes. BUT, I also like how Swift is practical in merging OOP and POP, and keeps itself to a reasonable complexity, which is why I prefer Swift over something like Haskell. Now, it might be your opinion that Swift - OOP = better Swift, but I think that this opinion is wrong. Adding abstract methods to Swift doesn't make it more complex, it just makes it even more practical, and subtracts nothing from it, not even in spirit.

1 Like

Having said that, I would like to go a step further than just adding abstract methods. I would also like "abstract typealiases", so that would probably call for proper abstract classes, not just abstract methods. This is so that an abstract class can adhere to a protocol that has associated types.
Obviously this is more complicated, but I would think that it still fits nicely into the current Swift OOP+POP model.

No that is not support. an abstract class or method is something that produces compile time errors when not implemented by a subclass. fatalError does not do that.

I donā€™t get the resistance to this. If people donā€™t want abstract classes and methods in your code base then donā€™t use them, use a linter to ban the keyword. At least let the people who want to use it, use it. Currently we donā€™t have a choice, whereas if implemented people would

7 Likes

Just don't use it argument doesn't really hold much water. If the discussion is instead about adding function keyword that is identical to func, just spelled differently, we probably all agree that the idea isn't very useful.

It's a slippery slope, yes, but it shows one important part of the discussionā€“the resistant is there because people think the utility added from abstract class doesn't outweigh the complexity introduced. People also still interact with the code they have little control of, so not-using-it isn't really a useful suggestion. There's also the discussion about the cohesion of the language as a whole.

Whether it is true that benefit abstract class is too little for the work involved (I'm not commenting on that) is to be debated, but it shouldn't simply be dismissed.

5 Likes

I think this case is slightly different. You can ignore this feature by simply not introducing any code that uses it. Subclassing from a class which does is going to incur the same costs and requirements as one that fakes abstractness by calling fatalError(), albeit with the benefit that it will be caught at compile time.

This seems qualitatively different to me than something like generics, or even property wrappers. If you don't understand those, it's hard to work with code that uses them.

2 Likes

If implemented in Swift, there is no obvious reason to me that this should only be restricted to classes. Maybe it would be better to add features to this affect to protocols so all implementing types (structs and classes alike) can benefit. This makes sense to me since abstract classes are basically protocols with a higher level of defined structure to them.

Protocols already offer a version of this feature. All protocols are effectively abstract, with default implementations providing the exceptions, not the rule. But inheritance of default implementations don't work the same way as inheriting methods from a superclass. To get the same benefits as abstract classes, protocols would have to change in what are almost certainly source-breaking ways.

1 Like

Except that this isn't true, unfortunately. Protocols don't conform to themselves, while abstract base classes do "conform" to themselves.

Protocols would work if a type could choose to conform to them or to inherit from them. We have no syntax to make that distinction.

Yes, and that's a second problem, not the same problem as the first one.

FWIW, I don't see what the fuss is, over adding abstract base types. Even if it were achievable with protocols (now or in the future), is there any harm in letting it also come in a handy, familiar, quick-use package?

2 Likes

While true, I don't see that as a function of their abstractness. The comparison is only with respect to a type that defines methods, but does not have implementations.

I think thatā€™s quite obviously not a useful idea.

Itā€™s not going to add complexity, itā€™s going to reduce it by moving a runtime behaviour to compile time. Itā€™s exactly the same as final, a proposal to remove final and instead replace it with checks in the initialiser that fatalErrors if the dynamic type is different to the classā€™s static type would be absurd.

Also people have to interact with other peopleā€™s code yes, but thatā€™s already just part of life, some code base ban the use of force unwrapping, others allow it. So to be more cohesive should we remove force unwrapping from the language?

As it stands now people have zero control over whether to use abstract classes.

3 Likes

Yes please. If I want my code to crash randomly deep inside third-party libraries, I have many existing options prior to Swift - C++, for example.

I don't actually have an opinion on this abstract classes topic, but I do agree with the position that languages should be opinionated and should coerce their users (responsibly and benevolently), because many of those users are not responsible. Allowing library authors to do things (such as your poignant implicit unwrapping example) that are convenient to them but not to the ecosystem at large runs a real risk of being a net loss.

(any retort along the lines of "don't use those libraries" is wonderful wishful thinking that doesn't address the practical problems around community building and finite resources)

Again, I don't know if any of the specific proposals in this thread run afoul of that - I'm just addressing the metaquestion.

2 Likes

Using forced-unwrapping as an assert is just another bug. I prefer crashes when the code encounters an unexpected condition. It's a lot better than the typical alternative of

guard let x = self.x else { return }

Trying to figure out why some method is returning without doing anything is a lot harder than getting a fatal error and then reporting the bug to the library developer.

3 Likes

Thinking this over a bit more, I think there are some features I've seen previously discussed that, if implemented, would allow Protocols to work for many of the same use cases for abstract classes.

  1. Add storage to Protocols. It needs to be possible for a stored Property declaration to live only in the protocol definition rather than also requiring definition in the adhering Type.
  2. Ability to easily and directly call the default implementations of protocol methods. I believe this is technically possible to do currently through some recipe of Type casting, but that's both verbose and obtuse.
  3. Improved UX when using Protocols with associatedtypes. Whether through automated Type Erasure, an expansion of some protocolName functionality, or another solution. Those Protocols need to be as easy to work with as a Class.
  4. Add visibility modifiers to Protocol. Proper OOP-encapsulation needs to be available for a powerful Abstract Class to work correctly.

I think those cover my usage-experience with abstract classes, but that's mostly limited to Java/Kotlin so there may be features from other languages I'm missing.There are also some other minor things that could/should be done to improve the ergonomics when working with the general design, but those are more incremental.

If these, or a similar set of features are likely to be added I think there's a good argument for not needing an official abstract class, however I haven't seen much movement on those fronts in a while. The original proposal was deferred 3 years ago with a similar set of shortfalls being mentioned regarding protocols.

4 Likes

Regardless of one's personal feelings about whether an abstract class is a "good" design, the defferal rational states:

Beyond any religious dogmas, Swift intends to be a pragmatic language that lets users get work done.

I think it's fair to say Swift should, or at least (based on the defferal rational) has previously intended to, support the design pattern of an abstract class. The question is whether it should be supported directly, through improvements to protocol, or via some other means.

Personally I see it as a question of the long-term design direction for protocol. If the relevant features will be added, that would be a more generalized and powerful API and abstract class shouldn't be necessary. If that isn't in the long-term plan for protocols, adding an abstract class would be a meaningful improvement for many codebases and shouldn't be that complex to implement.

2 Likes

Maybe 0026 has been deferred long enough to come to the following pragmatic conclusion: the protocol improvements that might replace it still are nowhere close to existing, and itā€™s still a pain point for many.

2 Likes

Well, in this case, I guess you'll basically end up with abstract classes under a different name - and imho protocols already have enough duties (see "normal" protocols and those with associated types), so adding another task makes Swift as a whole harder to understand than using an established name for an established concept.

1 Like

I agree, but you also don't want permanent features added to the language to address short-term deficiencies. If there's an intentional roadmap for the design, I'm ok with waiting a few more years for the "right" solution. If not...I think it should be seriously considered sooner rather than later.

Thereā€™s a difference in that value-semantic types can conform to protocols, but types which inherit from abstract classes must have reference semantics.

Also, abstract classes can have generic parameters whereas protocols canā€™t.

That brings to mind that adding properties to protocols would allow value types to have a poor man's method of inheritance. We already get a small amount of that from default implementations, but do we want to continue on this path by adding inheritable storage?

Sure, why not? It makes perfect sense to me for a protocol to be able to define properties, and I don't see it as the ā€œpoor man's inheritanceā€ but rather continuing down the path towards powerful mixins. There are issues with retroactive conformance that mean storage would probably only be provided automatically under some circumstances (e.g. when conformance was declared on the original type declaration, or on an extension in the same file, or similar) but you could be required to continue to do it manually in other cases.