> What irritates me is when people say classes are bad. Or subclassing is bad. Thatʼs totally false. Classes are super important. Reference semantics are super important. If anything, the thing thatʼs wrong is to say, one thing is bad and the other thing is good. These are all different tools in our toolbox, and theyʼre used to solve different kinds of problems.
The issues with inheritance based OOP are that it fits very few problems well, that it usually causes lots of problems and that many programming languages only have inheritance based OOP in their toolbox.
Java is the extreme case of this. Patterns like abstract visitor factories are hacks to express situations that cannot be expressed in an obvious way.
Inheritance is just one of multiple facets of safe code reuse in OOP. Aggregation, composition, encapsulation are as are much as fundamental notions in OOP as inheritance. So i think reducing OOP in general, and java in particular to "inheritance based OOP" is a miss characterization
> are that it fits very few problems well, that it usually causes lots of problems and that many programming languages only have inheritance based OOP in their toolbox.
Do you have any objective way to measure that ?
> Patterns like abstract visitor factories are hacks to express situations that cannot be expressed in an obvious way.
But isnt that the reason to have a pattern ? An easy way to expression a non obvious recurring situation ?
None of those things are necessarily fundamental notions in OOP so much as they are core constructs in many OOP languages. It's quite trivial to find multiple examples for why inheritance causes far more problems in many cases than when using composition- rigid class hierarchies and fragile superclasses are some such examples (problems: extendability/maintainability and high coupling). Patterns are ways to semi-cleanly work within an OOP design, but you'll find that it's often the case that a lot of verbosity needs to come along for the ride- working towards solving how to structure class hierarchies more than solving the actual problems those classes are meant to solve.
There's a reason why composition is preferred over inheritance. There's also good reason why some programmers will take it to the extreme and say that it ALWAYS causes more problems. Some languages lack of providing an appropriate aggregation alternative generally keeps inheritance alive.
Some people will hold onto their positive notions about inheritance too, and that's fine, but there's a reason why many people advocate strongly against it (and why some modern language designers skip it altogether!)
> Some languages lack of providing an appropriate aggregation alternative generally keeps inheritance alive.
I might not be understanding well, but are you talking of languages that happen to have no _composition_? But do have inheritance? Sounds like those with inheritance are a strict subset of those with composition (which in turn would be all but fringe languages).
No I'm talking about a lack of language constructs that make composition a pain and inheritance a quick fix: forwarding. Inheritance is often used improperly because writing forwarding methods to delegates is a pain. IS-A/HAS-A goes OUT-A the window: in many cases ushers developers to adopt it because it requires less typing.
It seems to me that you are just explaining what the parent was saying. My point is not inheritance is good/bad or usefull, my point was that OOP doesn't imply inheritance it's just one of the many ways OOP languages reuse code.
Aggregation and composition are almost the same, are not distinguished in e.g. Java and exist in pretty much every rogramming language regardless if they are object-oriented.
Inheritance is presented everywhere as the go-to method for structuring everything in languages supporting inheritance. The whole java class library consists of huge class hierarchies. Every non-trivial java code base I have seen is heavily infested with inheritance. Inheritance together with other forms of polymorphism are the biggest differentiator to structural programming in e.g. C.
Inheritance is everything else but safe: you have to check if you broke the behavior of all methods and public fields you are inheriting. How often do you go through all classes you are inheriting from?
Sometimes you cannot even fix this with overriding methods:
You can easily construct a cut off ellipsoid from a regular ellipsoid, but then it stops being a quadric surface invalidating your nice class hierarchy. And you cannot change the class hierarchy because half of your codebase depends on it.
Patterns are like neat useful tricks. Like how to open a bottle with a hammer. Pretty nice to open your beer at the end of the day in a workshop. If you work in a bar and you constantly have to use tricks to use your hammer for your work you should probably rethink if a hammer is the right main tool for you.
> Inheritance is presented everywhere as the go-to method for structuring everything in languages supporting inheritance. The whole java class library consists of huge class hierarchies.
That conclusion holds if your primary experience is Java: the Java class library is widely believed to have abused inheritance.
However other languages have avoided that trap. Apple's Cocoa frameworks in ObjC do use inheritance but also delegation, notifications, etc. Also Swift supports inheritance, but here we see Lattner describing inheritance as a "tool in our toolbox," not as the "go-to method for structuring everything."
> Inheritance is everything else but safe: you have to check if you broke the behavior of all methods and public fields you are inheriting.
Designing a class interface intended to be inherited is like any other API design exercise. Your API commits to invariants, and it's the client programmer's responsibility to follow them; if they do the class should not be broken.
If you find yourself checking "all methods and public fields," either the API is bad or you've misunderstood it.
Again, it's just one API design tool. Sometimes the alternative to inheritance is just an ad-hoc, bug-ridden re-implementation of inheritance.
> Inheritance is presented everywhere as the go-to method for structuring everything in languages supporting inheritance.
No, it's not. Many places recommend restraint in use of inheritance (and in languages that support only single inheritance, there are sharp limits to what it can do to start with.)
Patterns are just observed recurrences in a large sample of artifacts. Many people naively see the design patterns book as instructive, when in reality it is just retrospective.
> Aggregation, composition, encapsulation are as are much as fundamental notions in OOP as inheritance.
You say that as if these things are not just as easily expressed in FP -- if not even easier.
Aggregation is just records-of-records.
Encapsulation is just "abstract data types", e.g. ML modules with abstract members, or non-exported data constructors in Haskell. Another option would be simply closing over whatever you're trying to hide. Another option would be existential types. (There's some overlap among all of these.)
Composition... well, actually I'm not sure what exactly you mean by "composition" in the context of OO. Can you explain what you mean?
Reminds me of PHP CEO's tweet: "If you had bad experiences with scrum you just did scrum wrong. If you had good experiences making software you just accidentally did scrum".
> You say that as if these things are not just as easily expressed in FP
No, that's what you read. What i said what was i wrote... OOP is bigger than inheritance.
> -- if not even easier.
Again, this kind of statement really sound like empty FP propaganda to me.
> Encapsulation is just "abstract data types", e.g. ML modules with abstract members, or non-exported data constructors in Haskell. Another option would be simply closing over whatever you're trying to hide. Another option would be existential types. (There's some overlap among all of these.)
I think you may have misunderstood my intent. I was just trying to say "FP can do these things too".
> Again, this kind of statement really sound like empty FP propaganda to me.
Right, so any type of even very modest support for X is "empty X propaganda". Can we please assume at least a modicum of good faith here?
> Or "abstract data types" is just Encapsulation...
Oh, so "semantics" it is then. Oh, well.
FWIW, I think I'm right in saying that ADTs were invented quite a bit before OOP & "Encapsulation".
I notice that you also didn't answer my question of what Composition actually means in OOP. Do you have an answer? I promise, I wasn't being facetious.
That's easy, the word is well defined in the dictionary: a person or thing to which a specified action or feeling is directed. Some people try to call objects entities and claim they are doing something else, but they are just doing the same thing with different words.
What's "inheritance based OOP"? The kind of OOP that models everything using subclassing? Partly due to structural static typing so compatibility/polymorphism can only be achieved by having a common ancestor class?
Sure, but that's missing the point of OOP almost completely.
Anyway, subclassing is an extremely useful and by now somewhat underrated tool: it allows for unanticipated extension and programming-by-difference. Meaning you already have something that's close to but not quite what you need.
Inheritance based OOP models tree-like entites well, where hirachy is defined and clear cut. Unfortunately, lots of real life domains are best expressed by graphs - commonly a DAG. You need to pay attention to your edges and not just the nodes. Inheritance based OOP gives you one keyword to express your edges: extend, and it's horribly inadequate. Mutatable state is not a issue in Java OOP, lacking the expressive power is.
Trees can be modelled with sum types, the mathematical dual of product types (records). Java doesn't have sum types and so inheritance has to be used to encode them.
Subtyping adds huge amounts of complexity to type systems and type inference. Dart even chose to have an unsound type system because subtyping and parametric polymorphism (generics in Java) were deemed too hard for Google programmers to understand. The Go designers agreed.
Haskell and OCaml are a joy to program in, in part because they (mostly) eschew subtyping. So yes, subtyping is controversial.
>Dart even chose to have an unsound type system because subtyping and parametric polymorphism (generics in Java) were deemed too hard for Google programmers to understand. The Go designers agreed.
And they were both wrong, as millions of programmers use Java and C# and C++ just fine, and have created much more impressive software than what Dart or Golang programmers have. Plus, people used to the power of C++ would never switch to Golang (which is also what the Golang team observed: they mostly got people from Python/Ruby and that kind of services).
>Haskell and OCaml are a joy to program in, in part because they (mostly) eschew subtyping.
Sorry, but did you just said that "subtyping and parametric polymorphism (generics in Java) were deemed too hard for Google programmers to understand" (an argument based on complexity) and then went on to argue in favor of Haskell, which is notoriously difficult to grasp, and has so many foreign concepts that it makes Generics look like BASIC level concepts.
Ignoring all the abstractions and concepts (made possible by underlying simplicity), Haskell is absolutely a simpler language than C++ or Java with Generics.
EDIT: my point was that subtyping is controversial, I assume the downvotes are for my own personal position.
>Who are these people, were they professional programmers? How hard were they trying? Perhaps they just wanted a better Java?
How hard should they have tried? Should they have burnt the midnight oil?
This Java you talk about, is still in the top 1-3 languages by programmers, is it not?
And supposedly Haskell is easier (according to the parent comment) but at the same time needs people to try harder to get it than Java? It can't be both...
>I've worked on very large codebases in many large organisations with C++, Java and Haskell. Haskell certainly wasn't the horror story.
No, but it was the new novelty on greenfield staff. Easy to start as better. Java wasn't the horror story in 2000 either.
> This Java you talk about, is still in the top 1-3 languages by programmers, is it not?
I'm disappointed that you appear to judge technology based on popularity. By this argument, JavaScript trumps Java.
> And supposedly Haskell is easier (according to the parent comment) but at the same time needs people to try harder to get it than Java? It can't be both...
I can assure you, having learned both, that understanding Java and all its idioms/patterns is at least as hard as learning Haskell, especially if one seeks to build concurrent software (where the intricacies of Java's complex memory model cannot be ignored).
Because of the immense amount of investment already made, likely these people give up when they realise they cannot make much use of it in Haskell, at least not at first.
> No, but it was the new novelty on greenfield staff.
You've made an incorrect assumption. The Haskell codebases I have worked on, in both cases, have been large and over a decade old.
Anyway, my original post was about the controversy of subtyping, not Haskell versus Java.
He said Haskell was simpler than C++ which might be true. Doesn't mean it's easier. I'm more familiar with F# and C#. I'm guessing that the F# language is simpler in as far as having fewer language concepts. But many people struggle and would find it more difficult, at first. Brainfuck is even simpler yet even harder for many people.
Dart is not unsound (anymore), and they rely heavily on nominal subtyping. Ironically, typescript is unsound, and it relies heavily on more functional structural subtyping (in both cases soundness matters not much). Neither language has particularly good type inference. I believe Go is also structural, though without generics, its type system is bound to be simple either way.
Couldn't agree more