Classes turn out to conflate two or three different, unrelated concepts. Better languages split those things out so that you can use them separately. (Believe it or not, Java actually advanced best practices in this area: in C++ interfaces were just a "pattern", and many C++ fans were sceptical of the value of making a language-level distinction between interfaces and classes).
When we say "A extends B" we mean a) an instance of A composes in an instance of B b) A implements the interface that B implicitly expresses c) this implementation, by default, delegates to the instance of A from a). Sometimes you really do want to do all three of those things. But often you want to do some subset of them instead. So the language should give you ways to do parts of that without doing all of that, and then "extends" should be at most lightweight syntax sugar over that.
Sometimes you really do want to do all three of those things. But often you want to do some subset of them instead. So the language should give you ways to do parts of that without doing all of that, and then "extends" should be at most lightweight syntax sugar over that.
Sounds like you’re describing something like C.
Leverage individual header files to implement a specific behavior rather than features concretized in a spec we don’t always want.
Making everything a class, monoid, etc. feels like the programmer equivalent of making 9 Star Wars movies where each is comprised of lightsaber fights from open to close. That’s a cool still image but … really 9 movies huh?
Not at all. C has essentially no support for interfaces or polymorphism, yet alone delegation - even its data structure support is wonky (no real sum types). If C++ classes are "all you have is a hammer", C doesn't even give you the hammer, so you end up bashing screws in with a rock.
What I advocate is something like Rust, where you have both structs and traits as distinct concepts that serve separate purposes.
When we say "A extends B" we mean a) an instance of A composes in an instance of B b) A implements the interface that B implicitly expresses c) this implementation, by default, delegates to the instance of A from a).
This is probably the best summary of why traditional OOP is often suboptimal I've read. Clearly OOP solves (or solved) a problem that people were having, it's just that it tries to solve too many problems with the same system.
It's also worth noting that fans of "composition over inheritance" usually don't notice or mention that if you go with pure composition, you lose out on point (c) in most programming languages. Very few languages have a built-in mechanism by which a set of operations on type A can be automatically delegated to one of its elements, without at a minimum, listing all the operations to be delegated. So at least at present, traditional inheritance still provides an operation which is not available with composition.
We're starting to see languages have support for delegation - Kotlin has it and Rust was at least talking about it. I agree it's still pretty rare though.
Comments
Classes turn out to conflate two or three different, unrelated concepts. Better languages split those things out so that you can use them separately. (Believe it or not, Java actually advanced best practices in this area: in C++ interfaces were just a "pattern", and many C++ fans were sceptical of the value of making a language-level distinction between interfaces and classes).
When we say "A extends B" we mean a) an instance of A composes in an instance of B b) A implements the interface that B implicitly expresses c) this implementation, by default, delegates to the instance of A from a). Sometimes you really do want to do all three of those things. But often you want to do some subset of them instead. So the language should give you ways to do parts of that without doing all of that, and then "extends" should be at most lightweight syntax sugar over that.
Sounds like you’re describing something like C.
Leverage individual header files to implement a specific behavior rather than features concretized in a spec we don’t always want.
Making everything a class, monoid, etc. feels like the programmer equivalent of making 9 Star Wars movies where each is comprised of lightsaber fights from open to close. That’s a cool still image but … really 9 movies huh?
Not at all. C has essentially no support for interfaces or polymorphism, yet alone delegation - even its data structure support is wonky (no real sum types). If C++ classes are "all you have is a hammer", C doesn't even give you the hammer, so you end up bashing screws in with a rock.
What I advocate is something like Rust, where you have both structs and traits as distinct concepts that serve separate purposes.
This is probably the best summary of why traditional OOP is often suboptimal I've read. Clearly OOP solves (or solved) a problem that people were having, it's just that it tries to solve too many problems with the same system.
It's also worth noting that fans of "composition over inheritance" usually don't notice or mention that if you go with pure composition, you lose out on point (c) in most programming languages. Very few languages have a built-in mechanism by which a set of operations on type A can be automatically delegated to one of its elements, without at a minimum, listing all the operations to be delegated. So at least at present, traditional inheritance still provides an operation which is not available with composition.
We're starting to see languages have support for delegation - Kotlin has it and Rust was at least talking about it. I agree it's still pretty rare though.