Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Literally think about it. How else do you merge two structs if not using inheritance?

What? Using multiple inheritence? That's one of the worst ideas I've ever seen in all of computer science. You can't just glue two arbitrary classes together and expect their invariants to somehow hold true. Even if they do, what happens when both classes implement a method or field with the same name? Bugs. You get bugs.

I've been programming for 30 years and I've still never seen an example of multiple inheritance that hasn't eventually become a source of regret.

The way to merge two structs is via composition:

    struct C {
        a: A,
        b: B,
    }
If you want to expose methods from A or B, either wrap the methods or make the a or b fields public / protected and let callers call c.a.foo().

Don't take my word for it, here's google's C++ style guide[1]

> Composition is often more appropriate than inheritance.

> Multiple inheritance is especially problematic, because it often imposes a higher performance overhead (in fact, the performance drop from single inheritance to multiple inheritance can often be greater than the performance drop from ordinary to virtual dispatch), and because it risks leading to "diamond" inheritance patterns, which are prone to ambiguity, confusion, and outright bugs.

> Multiple inheritance is permitted, but multiple implementation inheritance is strongly discouraged.

[1] https://google.github.io/styleguide/cppguide.html#Inheritanc...



> Even if they do, what happens when both classes implement a method or field with the same name?

It's done in Java with interfaces with default implementations, and the world hasn't imploded. It just doesn't seem like that big of a problem.


> It just doesn't seem like that big of a problem.

It really, really depends on the codebase. There are absolute mammoth tire fire codebases out there - particularly in "enterprise code". These are often made up of insane hierarchies of classes which in practice do nothing but obscure where any of the actual logic lives for your program. AbstractFactoryBuilderImpl. Wild goose chases where you need some bizzaire and fragile incantation to actually create an object, because every class somehow references every other class. And you can't instantiate anything without instantiating everything else first.

If you haven't seen it in your career yet (or at all), you are lucky. But I promise you, hell is programmed by mediocre teams working in java.


I've seen such difficult to follow code in Java, and I complain about it every time. But having multiple inheritance isn't the cause (because Java doesn't have that). Nor does having multiple interfaces with default implementations have any real impact (at least in _any_ case I've seen).

The only real difference I see between multiple inheritance and multiple interfaces with default implementations is constructors. And they can be handled in the same way as default implementations; requiring specific usage/ordering.


I guess the point was never how to do thing properly, but:

"how to join two struts with least amount of work and thinking so my manager can tick off a box in excel"

in such case inheritance is a nice temporary crutch


>What? Using multiple inheritence?

You just threw this in out of nowhere. I didn't mention anything about "multiple" inheritance. Just inheritance which by default people usually mean single inheritance.

That being said multiple inheritance is equivalent to single inheritance of 3 objects. The only problem is because two objects are on the same level it's hard to know which property overrides which. With a single chain of inheritance the parent always overrides the child. But with two parents, we don't know which parent overrides which parent. That's it. But assume there are 3 objects with distinct properties.

   A -> B -> C
would be equivalent to

   A -> C <- B. 
They are isomorphic. Merging distinct objects with distinct properties is commutative which makes inheritance of distinct objects commutative.

   C -> B -> A == A -> B -> C
>I've been programming for 30 years and I've still never seen an example of multiple inheritance that hasn't eventually become a source of regret.

Don't ever tell me that programming for 30 years is a reason for being correct. It's not. In fact you can be doing it for 30 years and be completely and utterly wrong. Then the 30 years of experience is more of a marker of your intelligence.

The point is YOU are NOT understanding WHAT i am saying. Read what I wrote. The problem with inheritance has to do with human capability. We can't handle the complexity that arises from using it extensively.

But fundamentally there's no OTHER SIMPLER way to merge two objects without resorting to complex nesting.

Think about it. You have two classes A and B and both classes have 90% of their properties shared. What is the most fundamental way of minimizing code reuse? Inheritance. That's it.

Say you have two structs. The structs contain redundant properties. HOW do you define one struct in terms of the other? There's no simpler way then inheritance.

>> Composition is often more appropriate than inheritance.

You can use composition but that's literally the same thing but wierder, where instead of identical properties overriding other properties you duplicate the properties via nesting.

So inheritance

   A = {a, b}, C = {a1}, A -> C = {a1, b}
Composition:

   A = {a, b}, C = {a1}, C(A) = {a1, {a, b}}
That's it. It's just two arbitrary rules for merging data.

If you have been programming for 30 years you tell me how to fit this requirement with the most minimal code:

given this:

   A = {a, b, c, d}
I want to create this:

   B = {a, b, c, d, e} 
But I don't want to rewrite a, b, c, d multiple times. What's the best way to define B while reusing code? Inheritance.

Like I said the problem with inheritance is not the concept itself. It is human nature or our incapability of DEALING with the complexity that arises from it. The issue is the coupling is two tight so you make changes in one place it creates an unexpected change in another place. Our brains cannot handle the complexity. The idea itself is fundamental not stupid. It's the human brain that is too stupid to handle the emergent complexity.

Also I don't give two flying shits about google style guides after the fiasco with golang error handling. They could've done a better job.


> But assume there are 3 objects with distinct properties...

Why would we assume that? If the objects are entirely distinct, why are you combining them together into one class at all? That doesn't make any sense. Let distinct types be distinct. Let consumers of those types combine them however they like.

> But fundamentally there's no OTHER SIMPLER way to merge two objects without resorting to complex nesting. [...] You have two classes A and B and both classes have 90% of their properties shared. What is the most fundamental way of minimizing code reuse? Inheritance. That's it.

So now the objects have 90% of their properties shared. That's different from what you were saying earlier. But moving on...

The composition answer is similar to the inheritance answer. Take the common parts and extract them out into a self contained type. Use that type everywhere you need it - eg by including it in both A and B.

> given this: A = {a, b, c, d} I want to create this: B = {a, b, c, d, e}. [...] What's the best way to define B while reusing code?

Via composition, you do it like this:

    B = { a: A, e }
What could be simpler than that?

You keep claiming that inheritance is simpler. But then you go on to say this:

> The problem with inheritance has to do with human capability. We can't handle the complexity that arises from using it extensively. [..] It is human nature or our incapability of DEALING with the complexity that arises from it. The issue is the coupling is two tight so you make changes in one place it creates an unexpected change in another place. Our brains cannot handle the complexity.

In other words, using inheritance increases the complexity of the resulting code for the human brain. It makes our code harder to read & understand. I absolutely agree with this criticism you're making.

And that's an incredibly damning criticism, because complexity is an absolute killer. Your capacity to wrangle the complexity of a given piece of code is the single greatest limitation of any software developer, no matter how skilled. Any change that makes your code more complex and harder to understand must be worth it in some way. It must pay dividends. Using inheritance brings no benefit in most cases over the equivalent compositional code. The only thing that happens is that - as you say - it makes the software harder for our brains to handle.

If you ask me, that's a terrible decision to make.


>Why would we assume that? If the objects are entirely distinct, why are you combining them together into one class at all? That doesn't make any sense. Let distinct types be distinct. Let consumers of those types combine them however they like.

Human = torso, legs, arms. Three distinct objects combine into one thing. A human by definition is the union of these things. It's fundamental. It's just your bias is trying to see it as something else.

>So now the objects have 90% of their properties shared. That's different from what you were saying earlier. But moving on...

So? I can talk about multiple things right? This is allowed in life right? Did i break the law here?

>The composition answer is similar to the inheritance answer. Take the common parts and extract them out into a self contained type. Use that type everywhere you need it - eg by including it in both A and B.

Composition is the same thing. But it's saying instead of overriding duplicate properties, Clone the duplicate property. That's it. And it uses nesting to achieve this. This arbitrary rule isn't more fundamental then overriding the duplicate property.

>Via composition, you do it like this:

Why don't you take a look at my examples again. You are either not able to comprehend or you didn't read it. I literally said the same thing:

   B = {a: A, e}
The above a complete dupe of what I wrote.

   B = {a, b, c , d, e}
Just nested. Which I brought up:

   B = {{a, b, c, d}, e}
Is it not? Please read my response replying with stuff like this. Read it thoroughly.

>And that's an incredibly damning criticism, because complexity is an absolute killer.

Not exactly it's not that straightforward. Because inheritance minimizes code copying. It reuses code in the most efficient way possible. So actually lines of code and duplicate code actually goes down. So complexity in one area falls and rises in another area.

Our brains are biased towards handling complexity of duplicate code better then tightly coupled code.


> Human = torso, legs, arms. Three distinct objects combine into one thing. A human by definition is the union of these things. It's fundamental. It's just your bias is trying to see it as something else.

No, it’s not. If I put a torso, legs and arms (and perhaps a head) on a table, I don’t get a human being. I’d say a human composes all of those things (and more!). But a human doesn’t inherit from them. For example, each leg can kick(). But you can’t inherit from two legs! And if you did, which leg is the one that kicks when you call the function? Much better to have human class which contains two legs. Then human can have a kick(RIGHT_LEG) function which delegates its behaviour to right_leg.kick().

There’s lots more ways composition helps us model this. Let’s say we want to model blood temperature, which is tracked in every limb separately. Composition makes it more straightforward to have different behaviour (& state) in the Body class for blood temperature than in any of the limbs. (Eg maybe the blood temperature is the average of all limbs temperature. That is more straightforward to implement with composition.)

> inheritance minimizes code copying. It reuses code in the most efficient way possible.

On the surface, I agree with this claim. But it’s funny - programs which make heavy use of OO always seem unnecessary verbose. I wonder why that is? I’m thinking of Java where it’s common to see utility classes that just have 1 or 2 fields take up 100+ lines of code, due to class boilerplate, custom hashCode, toString and isEqual methods and all the rest.

But in any case, as you say, we aren’t just trying to optimise for the fewest lines of code. We’re trying to optimize for how easy something is to read, write and maintain. Adding code to increase simplicity is often worth it. Inheritance increases complexity because it adds a layer of hidden control flow. When I’m reading a program, I need to do a lot of work to figure out if foo.bar() is calling a function in one of the base classes or in the derived class. As you say, humans don’t deal with that kind of complexity well. In general, explicit is better than implicit - this.leg.kick() is more explicit than this.kick() when kick() exists somewhere in one (or more?) of the base classes.

Also let’s say I have 3 classes A, B extends A and C extends A. If there’s a bug in B that involves something in the base class A, fixing that bug may break implicit invariants in C. This kind of “spooky action at a distance” is horrible. Ironically, it violates the principle of encapsulation that OO claims as one of its core principles. I find this kind of problem is rarer in compositional systems. And when it happens, it’s usually much more straightforward to debug and fix. The reason is because classes are all self contained. You don’t have partially-specified base classes that only kinda sorta maintain their invariants. And derived classes don’t implicitly include their base class’s behaviour. As a result, there’s less implicit entanglement. B and C can much more easily change how they wrap A’s behaviour.

At the end of the day, I think we more or less agree that inheritance makes code harder to reason about. I don’t write code to express a pure conceptual representation of the world. (And neither should you!). I write code to get stuff done. If inheritance makes it harder for humans to be productive with our software, then that’s reason enough to abandon it.


I’m not gonna read the full thing. I’m just responding to the first part. Composes or inheritance is just vocabulary for two things that are the same with slight differences.

Composes duplicates identical properties via nesting.

Inheritance overrides properties that are identical.

That’s it. I’m done.


Maybe that's our ultimate disagreement. I think the difference between composition and inheritance matters a lot. It changes how we break our software into modules. It sounds like you think of inheritance as being fundamentally the same as composition "with slight differences". Although even you admit that "Our brains cannot handle the complexity [of inheritance]". If that's true (and I think it is), the difference is surely more than skin deep.

I agree about nesting. But nesting matters, because it forces us to design components which make sense in isolation. As a result, composition encourages - and in many ways requires - better modularity in code. Inheritance does not. Base classes are often poorly conceived, poorly specified grab-bags of state and functions. They lead to hard to understand, hard to follow code.

Earlier in this thread you insulted my intelligence. You said this:

> Don't ever tell me that programming for 30 years is a reason for being correct. It's not. In fact you can be doing it for 30 years and be completely and utterly wrong. Then the 30 years of experience is more of a marker of your intelligence.

I'm curious if you'll still back the argument you've made here after you've been programming for 30 years too. You're clearly already suspicious of how and why inheritance makes code harder to understand. I suspect in a few years, you'll come around to my point of view on this. But I'd love to know if I'm wrong.


>I'm curious if you'll still back the argument you've made here after you've been programming for 30 years too. You're clearly already suspicious of how and why inheritance makes code harder to understand. I suspect in a few years, you'll come around to my point of view on this. But I'd love to know if I'm wrong.

I did insult your intelligence. Because when you said you have 30 years of experience I hear total arrogance. It's like "I'm right and you're wrong because I have 30 years of experience" When I hear that I just want the other person to shut the hell up.

>I agree about nesting. But nesting matters, because it forces us to design components which make sense in isolation. As a result, composition encourages - and in many ways requires - better modularity in code. Inheritance does not. Base classes are often poorly conceived, poorly specified grab-bags of state and functions. They lead to hard to understand, hard to follow code.

Again, you resuse the same code if you don't use inheritance. A cat walks, so does an animal, do does a dog. You have to write walk() twice if you don't use inheritance. There's a trade off here.

The difference is skin deep. It's the emergent complexity that is NOT skin deep.

combining objects via "object composition" or "inheritance" is different. One way is not more right then the other. It's simply that you can't handle the hierarchical relationships.

But think about it. If you have a deeply nested Object where you don't use inheritance. Then all the objects have multitudes of redundant properties, doesn't that result in complex code as well? And how does nesting objects make it less complex then inheriting objects? It's more of code navigation problem in the sense that when you use inheritance and you look at a child derived from generations of inheritance it's just hard to read and figure out what the final object is.

With object composition the view is the same. You have an object that holds generations of nested objects. The difference is you can control click and follow the definition of the nested object so it's more visible.

Thus it seems to me the issue with the complexity is that inheritance simply does not give you a widget you can control click into easily to follow the definition. This whole problem could be characterized by a user interface issue because it's not evident to me how an object with nested objects 1000000 layers deep is more complex then the same object derived from 100000 ancestors.


> The difference is skin deep. It's the emergent complexity that is NOT skin deep.

Yeah I agree with this. There's something about inheritance is more than skin deep. Something which changes how we conceive of our software. I agree that whatever that is, its quite important and impactful.

I could talk for days on what I think that difference is. I wrote a whole bit, but deleted it because I think I've said enough about what I think.

What do you think the difference is? How is it possible for composition and inheritance to be so different, if they're so alike on the surface?


I literally wrote it above and you didn’t read. It’s a user interface issue, Let me copy it here:

But think about it. If you have a deeply nested Object where you don't use inheritance. Then all the objects have multitudes of redundant properties, doesn't that result in complex code as well? And how does nesting objects make it less complex then inheriting objects? It's more of code navigation problem in the sense that when you use inheritance and you look at a child derived from generations of inheritance it's just hard to read and figure out what the final object is because there’s no easy way to visualize or follow the derived properties.

With object composition the view is the same. You have an object that holds generations of nested objects. The difference is you can control click and follow the definition of the nested object so it's more visible.

Thus it seems to me the issue with the complexity is that inheritance simply does not give you a widget you can control click into easily to follow the definition and see what the derived methods are.

This whole problem is characterized by a user interface issue because it's not evident to me how an object with nested objects 1000000 layers deep is more complex then the same object derived from 100000 ancestors.

Think about the emergent complexity here. An object derived from inheriting a chain of 1000 inherited objects is actually less complex then that same chain of objects created via composition. Because duplicate properties don’t get overridden you have more data stored here then inheritance. It’s actually more complex.

The problem is in the user interface.

Create an IDE that automatically fills out all the derived methods of an object and allows you to control click to the ancestor where the derived method comes from and the issue seems to me to be solved.


> Say you have two structs. The structs contain redundant properties. HOW do you define one struct in terms of the other? There's no simpler way then inheritance.

why inheritance would make it easier?


I have: A = {a, b, c, d, e}

I want to write: B = {a, b, c, d, e, f, g}

But I don't want to write duplicate code

So I write:

   B = B(A) = {A, e, f, g} 
aka I use inheritance.

Are there easier ways? No. Inheritance is the most fundamental way of doing this. Composition is just a work around as it results in arbitrary nesting. But ultimately it's the same thing too.


The real major issue with multiple inheritance is that only a few languages (Eiffel, for one) seem to require declaring invariants for each class.

Then multiple inheritance is much cleaner because you can test constructor arguments against the union of these invariants before even hitting a constructor.

Note that I’ve never used it, but it did strike me that this was “the” way to include multiple inheritance in a language. But for some reason (run time performance maybe?) no one else seems to have done it this way.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: