Not really a fan of required, but that would be a possibility.
I just think that if you allow open classes, then you are admitting that these classes will have some functionality provided by subclasses. From there it immediately follows that abstract classes are needed. If you can say open, you must also be able to say abstract. Anything else is an omission from the language, which one can obviously live with, but why?
Yes, but I would argue that if a third party framework requires subclassing an open class and it isn't a legacy interface from Objective-C, then its a "code smell" in that framework.
Even with Objective-C, we have delegates and data sources to attempt to reduce the amount of subclassing you need to do to change behavior.
The only instance of an open class in the stdlib is the Encoders/Decoders, which IMHO is a mistake as the internals needed to change encoding/decoding behavior are not externally accessible. I also feel that in this case it is a sign of a bad API - the reason that Encoders have to be classes is because the design of the container interface behind Encodable mandates containers have reference semantics.
I am currently implementing a Parsing DSL where using open classes works very well, and I rather like the smell of it. It is not published yet, but I can already give an example:
You create a grammar in my DSL by subclassing from Grammar which is an open class. This is very natural, and I don't see anything smelly about that. It would not be possible to use a protocol instead, as Grammar keeps track of internal state that the user of the library should not be exposed to.
That looks like I have to repeat grammar. a thousand times there, just in order to adhere to a dogma ... ;-) Doesn't look like an improvement. But playing the devil's advocate here, could I get rid of the grammar repetition in your example somehow, via something like import grammar._ ?
I'd say fatalError is an anti-pattern here. Abstract classes are not an anti-pattern if properly used, just like continue and break are not an anti-pattern if properly used. If you don't know when to use a tool, that's a different issue entirely.
I think the most you can do is use a short name (or just $0). If we could do something like kotlin's apply it would allow using it, but I'm doubtful we'll see something like that in Swift anytime soon.
I don't think I went that far...regardless there are a lot of (IMO) valid use cases for that type of structure in an OOP ecosystem. Swift having structures that enable other solutions doesn't invalidate all use cases
Actually, I think the above suggests an easy implementation of abstract classes: Just introduce an abstract qualifier as shown above, and say that the semantics is the same as if the body would have been implemented via fatalError. That is an easy rewriting step. Then go ahead and support abstract in the type system via warnings / errors at compile time.
No, you misunderstand me. If you find an abstract class in your codebase there may be other problems to solve that use more functional rather than object-oriented solutions. It's rarely a case of interface design, the trees, and rather symptomatic of your holistic architecture, the forest.
So just to iterate on that, there would be no such thing as an abstract class, but just abstract initialisers / methods. That should help with dealing with corner cases I've seen people talk about in the evolution mailing list. It is very similar to what @GetSwifty mentioned earlier.
I would also tend towards finding a more functional/POP solution but Swift, as a language, isn't dogmatic about specific design patterns. OOP has a long history and a massive user/knowledge base. Swift supports users who prefer that approach as well as users who prefer a functional approach. Not to mention it's current core usage is in one of the largest OOP-library ecosystems around.
One of the things that I was hoping to see is an extension of Compiler Diagnostics (SE-1096) within a particular compile path.
What I mean is more akin to the constexpr if type of thing in C++, where if we have:
In the example above with abstractMethod invoked in the base implementation that simply calls fatalError, instead you would just put an #error(...) in the base implementation.
During compilation, if the compiler detects that the base implementation is invoked, it will trigger the compile error.