If we cannot assume synchronous execution, then semantics can break exactly as you said. That a rather orthogonal problem from the inheritance one and my suggested solution is to make use of pure functional programming (hence purity can be assumed unless the method signature indicates that an effect might happen). Here is an example of how a potentially async Stack might look like:
The IO would describe an action that can be ran at a later point, which means when the method has being called and returned, nothing has happened until you call `run(io)`.
That way you will be immediately aware of the behavior and adapt your Countstack as follows:
That ensures that order of events is being kept and also that failures (e.g. a concurrent thread dies in the middle) are handled.
> Assuming that the Stack class specifies that all modifications will go through the push and pop methods
This is a severe restriction on how the Stack can be implemented then and it bears the risk of someone violating this specification by accident (think about the famous equals/hashCode specification in Java).
You can push things into the type system only so far before you drop off a cost/benefit ratio cliff, unfortunately. Fundamentally, even in FP languages that expose side effects in the type system, you can still easily make APIs with undocumented semantics and in which users will break as the underlying component evolves. In fact Haskell is quite notorious for an ecosystem that seems to believe Haskell's type system obviates the need for good documentation, an illusion the Java world fortunately never labored under.
I think in recent years there's been an uptick in clever sounding attacks on common PL features like inheritance and exceptions, many of which look suspiciously like motivated reasoning. At the very least the arguments are extremely weak. This article ends by saying:
"Personally, for code reuse and extensibility, I prefer composition and modules."
But these are orthogonal. Languages like Kotlin have built-in support for inheritance, modules and composition. It's not an either/or approach, and there are lots of high quality, highly successful codebases that use inheritance extensively which would be pretty unimaginable without it. I use libraries that use inheritance every day and it's very rarely a problem: only in cases where someone made a bad API with it, and you can get bad APIs that rely on composition or badly modularised APIs too. I don't feel like one problem is more common than another.
If we cannot assume synchronous execution, then semantics can break exactly as you said. That a rather orthogonal problem from the inheritance one and my suggested solution is to make use of pure functional programming (hence purity can be assumed unless the method signature indicates that an effect might happen). Here is an example of how a potentially async Stack might look like:
The IO would describe an action that can be ran at a later point, which means when the method has being called and returned, nothing has happened until you call `run(io)`.That way you will be immediately aware of the behavior and adapt your Countstack as follows:
That ensures that order of events is being kept and also that failures (e.g. a concurrent thread dies in the middle) are handled.> Assuming that the Stack class specifies that all modifications will go through the push and pop methods
This is a severe restriction on how the Stack can be implemented then and it bears the risk of someone violating this specification by accident (think about the famous equals/hashCode specification in Java).