33 ms·
Inheritance was invented as a performance hack
- marcodiego 6y agoI was once one of the "luky 10.000"[0] about this: https://www.snopes.com/fact-check/program-management/ https://www.snopes.com/fact-check/program-management/ [0] https://xkcd.com/1053/ https://xkcd.com/1053/
- BoorishBears 6y agoIf it has a Snopes article debunking it, it's the inverse of the lucky 10,000 since "everyone knows" the wrong fact
- redis_mlc 6y agoThe "interview" in snopes is a parody, but if you read it carefully, was based on pretty solid facts about lack of class reusability across companies, and complexity. (One argument for the limited adoption of lisp is that everybody has their own little internal or even personal non-standard libraries.) Ironically a lot of companies that wrote libraries in C++ ended up "downgrading" them to C for better cross-platform compatibility with various linkers.
- dmix 6y agoSeems like a cool era to be working on computers. For lack of a better phrase, the world was your oyster.
- catern 6y agoYes, I agree - you had much fewer pre-existing ideas to guide you, and really good new ideas were coming out all the time and you could pick between them - you could do all kinds of innovative things and they would really be novel. Although today we have a much better grasp on formal semantics, which makes it easier to make up new ideas. But those new ideas aren't likely to ever displace the old ideas popularly, and it's hard to come up with them in the first place since old ideas live in your head...
- rectang 6y agoFine. But obviously it caught on for other reasons, like code reuse and an intuitive mental model. The HN appetite for posts ragging on inheritance will never be sated.
- SilverRed 6y agoWhat benefit does inheritance provide over importing files containing functions? Ruby and loads of other languages let you import files and those functions work like they were part of the class itself.
- rectang 6y agoMany benefits (e.g. attaching behavior to instances rather than modules, though there are other ways to accomplish the same goal), many drawbacks (fragility, yoyoing, etc.). C could be described as "files containing functions", and C++ was originally "C with classes", so it's not like "files with functions" is a revolutionary idea. This is all extremely well-traveled ground. I'm happy these days using languages like Rust and Go that avoid classical inheritance, so I'm not here to argue in favor of inheritance. I'm just irritated by the pile-on.
- fiddlerwoaroof 6y agoI’d also add that live systems like Smalltalk avoid a lot of the fragility and dynamism of inheritance by the way the browsers show all the currently active extensions to the class, and all the current subclasses in a fairly compact view. It’s a lot easier to find and modify affected child classes in this way than it is with “code in files” systems. Also, the way CLOS does inheritance avoids a lot of the drawbacks too by decoupling classes and inheritance from behavior.
- deleted 6y ago[deleted]
- FpUser 6y agoThese are those different features. Delphi / ObjectPascal for example support either. As for what benefit - your example with Ruby can be reversed and I can ask you the same question. Anyways large part of a programming is an art and different people have different approaches. If you do not like the concept do not use it. Just do not assume that what you think is the "right way".
- fiddlerwoaroof 6y agoI think discussions of OOP and inheritance often miss the influence of Knowledge Representation on OOP: I’m not entirely clear of the history myself, but I’ve gotten the impression that knowledge representation research (things like frame systems) was a semi-independent influence on the design of OO systems
- mycall 6y agoc2 has some interesting thoughts on the topic. https://wiki.c2.com/?KnowledgeRepresentations https://wiki.c2.com/?KnowledgeRepresentations https://wiki.c2.com/?TypeInferenceStory https://wiki.c2.com/?TypeInferenceStory https://www.cin.ufpe.br/~mtcfa/files/in1122/Knowledge%20Representation%20and%20Reasoning.pdf https://www.cin.ufpe.br/~mtcfa/files/in1122/Knowledge%20Repr...
- dleslie 6y agoHow is it that c2 has remained so quality?
- fiddlerwoaroof 6y agoIt turned write-only before the Eternal September of wikispam set in
- hinkley 6y agoI am trying to start a wiki project and I have no idea what sort of world of stupidity I’m about to step in to, do I? I had thought of building the history system on top of a theory of patches system would be a nice stretch goal. But it seems to me that having an infinite undo function drastically changes the account creation situation. I only have to be passingly sure you’re not a bot, and I can decide after the fact if you’re approximating a decent human being or not.
- fiddlerwoaroof 6y ago
- whoisburbansky 6y agoI spent way too long trying to figure out how giving your kids your lifetime's earnings would be a performance hack beyond, you know, giving them a head start, before realizing what the headline was talking about.
- MeinBlutIstBlau 6y agoI figured cause it was on HN that there was no way that was the article
- esyir 6y agoPeople care tons about their kids -> Working hard and creating value results in a larger inheritance for their kids -> Exploit biological desires to protect offspring to get yourself to be more productive?
- IncRnd 6y ago"Working hard and creating value" and "biological desires" are multiple inheritance, which is outside the scope of this article.
- vbsteven 6y agoIs this the diamond problem?
- sjfidsfkds 6y agoYou just invented society!
- deleted 6y ago[deleted]
- hkmurakami 6y ago"Jane/John Doe, eat your carrots and finish your homework before 9pm, or who knows what might happen to your interest in the family trust" is a conversation that has probably happened somewhere in history. On a more serious note, thinly veiled threats by patriarchs to their children working in the family business regarding their eventual share in the business is something that probably happens routinely around the world, especially in areas like Europe, East Asia, South Asia, and South East Asia, with many multi-generational privately held family businesses.
- thewickedaxe 6y agoI'm a little confused. Inheritance, types and all that stuff are just language concepts right? Simula may have been one of the first to implement inheritance as a means to an end, but how could they have "invented" inheritance? I'm sure the idea of inheritance was already there in language proofs. [I might be really really wrong here]
- username90 6y agoTheory usually comes after practice. People create something that works, then theorists formalizes it into a well defined concept.
- vincnetas 6y agoJust like some artists create art? First they do then they explain.
- nathias 6y agoidea > practice > formalization or idea > formalization > practice
- Arwill 6y agoI hold the view that programming patterns were invented as solutions to technical problems. It was then later that some theoretical "usefulness" or "elegance" was attached to them. One should know the technical benefits and drawbacks of programming patterns before applying them. Just because some language or environment or majority of programmers promotes it, doesn't mean that its ok to apply it. Without knowing the technical reason for applying a pattern is just cargo cult programming.
- lou1306 6y agoMany language features followed that route, yes. Languages themselves are typically first introduced, and then (maybe) formalized. Meanwhile, some other "patterns" or features, like higher-order functions or recursion, were originally introduced in the context of theoretical CS (e.g. they can be described in the lambda calculus), and it took some effort to implement them in practice.
- TeeMassive 6y agotl;dr: > Simula created inheritance instead of using composition because it allowed their garbage collector to be simpler. In C++, inheritance (and other OO languages I assume) is implemented as a composition and when multiple inheritance of the same class happens then the members of the twice parent is inherited twice unless the virtual keyword is used. ( in other words, if B : A, A, C then sizeof B = A + A + C + B's stuff, or if B : virtual A, virtual A, C then sizeof B = A + C + B's stuff). This is why declaring on the stack Parent parent = Child() "works" by just truncating the content of the child that was appended at the end of Parent. Personally I like to use private inheritance as a composition shortcut since for all intent and purpose it's achieves the same goal with less boilerplate. Outside of that I do use it if it strictly follows the substitution principle with other the members of the base class being used just as much as the child's. Which happens very rarely but does happen. It's like an Allen's key, you probably won't need it except when you must disassembled your IKEA furniture when moving out.
- unkulunkulu 6y agoDoesn’t Parent parent = Child() work because Parent’s constructor is run by implicitly casting ref-to-Child to ref-to-Parent?
- fauigerzigerk 6y agoI don't think that's enough, because Child() still results in an object that is sizeof(Child). In order to store that object in a variable that is sizeof(Parent) it has to be rejigged somehow. I think what TeeMassive is saying is that this rejigging is a simple truncation because the memory layout of (non virtual) inheritance in C++ is a concatenation of Parent and Child objects. I'm not sure whether this is implementation specific though.
- unkulunkulu 6y agoThat’s certainly enough, if you can call a constructor, you don’t have to think about how it works and that the classes are related. The part about “rejigging” and simple truncation happens before the constructor call when you cast ref-to-Child to ref-to-Parent.
- valenterry 6y agoAfter reading all the comments of many confused and curious, here is when inheritance is bad and why so _in the absence of any performance considerations_. First, inheritance from an interface/trait is totally okay. The problem is class inheritance, meaning implementation inheritance. There are two cases: 1) you inherit from a class and only add methods but don't overwrite anything. This is the good case, you can do that 2) you inherit from a class and overwrite one ore more of its methods. This is the bad case, no matter how you look at it. And here is why. Look at the following classical simple Stack class: class Stack(...) { def push(element) returns nothing = ... def pushAll(elementlist) returns nothing = ... def pop() returns element = ... } Now imagine you want to keep a count of the number of elements, so you decide to extend the class: class CountingStack(...) extends Stack { override def push(element) returns nothing = count += 1; super.push(element) override def pushAll(elementlist) returns nothing = count += elementlist.size; super.pushAll(elementlist) override def pop() returns element = count -= 1; super.pop(element) } Looks good no? Can you spot the problem? If the Stack class implements pushAll as iterating over the elementlist and calling push for each element, then the CountingStack will count twice! We could try to just not overwrite pushAll in CountingStack - but what if the Stack class is later changed to implement pushAll without calling push? Then CountingStack would now miss counts. This problem is impossible to fix and it is a general problem - when overwriting a method, you can never be sure that semantics could break when the base class is changed. So what is the solution here? Simple: use composition over inheritance. class CountingStack(underlyingStack) { def push(element) returns nothing = count += 1; underlyingStack.push(element) override def pushAll(elementlist) returns nothing = count += elementlist.size; underlyingStack.pushAll(elementlist) override def pop() returns element = count -= 1; underlyingStack.pop(element) } Problem solved - no matter how Stack is implemented, our CountableStack will always be correct. The drawback (if you want to call it one) is that we need some common interface/trait that both Stack and CountableStack will implement. I hope that sheds some additional light on why (implementation) inheritance is often considered a bad practice.
- titzer 6y agoThere are lots of bad uses of implementation inheritance, but it's not all bad. One pattern I use a lot is the "just these 5 missing methods". The base class might be complicated and large, with a lot of logic driving the process, but it needs to have 5 specific functions that it calls. One way is to have that big base class have almost all the logic and then 5 abstract methods and expect a subclass to implement those 5 methods and not override anything else. While the base class could in theory accept 5 first-class functions, or an object with 5 methods, in some cases those 5 methods are intricately tied to the operation of the whole thing and it doesn't make sense to separate them. That's a perfectly safe and clean use case of implementation inheritance.
- colineartheta 6y agoInheritance in general seems like a bad idea regardless of the framework, a leftover of the capitalist system that perpetuates injustice by the non-meritocratic transfer of wealth.
- nyanpasu64 6y agoThis article discusses object/class inheritance in programming languages, not wealth inheritance between people.
- colineartheta 6y agoThere’s a difference?
- mashpoe 6y agoIn case you aren't joking, inheritance in the context of computer programming (for simplicity's sake) is just a metaphor that's supposed to make computer programming easier and has nothing to do with economics.
- anonytrary 6y agoPretty off topic comment, but I read the title as "inheriting money is a way avoid being a high performer", which happens to be true, although completely irrelevant to the actual article. ;)
- dudeinjapan 6y agoI read it similarly as "inheriting money". And it's true that inheritance IS a performance hack--not for the children, but for the parents. Inheritance incentivizes parents to accumulate wealth not just for one lifetime but for multiple lifetimes. (Granted it may paradoxically have the opposite effect on the children as you mention.) Here's Milton Friedman on the matter: https://www.youtube.com/watch?v=km9OCw3f5w4 https://www.youtube.com/watch?v=km9OCw3f5w4
- gher-shyu3i 6y agoWhat's wrong with someone wanting to provide for his offspring?
- carlhjerpe 6y agoTed Kazinsky was talking about it in his manifesto. https://unabombermanifesto.com/#THE%20POWER%20PROCESS https://unabombermanifesto.com/#THE%20POWER%20PROCESS It depends on how much you provide for your offspring according to this deranged terrorist. And I've read stories from billionaire kids not living "the good life" because they never have to fight/work for anything.
- jokethrowaway 6y agoThat's just bad parenting. I think rich people tend to be people obsessed with work, giving access to money is easier than teaching their kids to earn stuff - it is a different problem.
- gher-shyu3i 6y ago
- qalmakka 6y agoIn my experience, 90% of the time people use inheritance but they really only cared about composition, and their language simply does not have any convenient facility to compose types and re-export their methods. With a good type system that includes traits, you almost never need virtual dispatching, as any Rust developer may tell.
- cout 6y agoThat's also been my experience. I've also seen programmers use composition and delegation solely to avoid inheritance (in a language that makes it natural), and the result is often less maintainable than a design that just uses inheritance.
- SMFloris 6y agoRecently experimented a bit with Rust and I found the reverse to be true. You cannot compose types in Rust. You compose behaviors not types. Very important distinction as I found out the hard way. Take the following example I have found on the net: https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=52feae5c84edb7942d05c8bf112b8237 https://play.rust-lang.org/?version=stable&mode=debug&editio... In that example, both the bicycle and the car have the property `speed`. Imagine you have multiple types now that need to have the `speed` property. You would need to copy-paste the same code for each new type in order for you to be type safe. Apparently it is called *Monomorphization*: https://cglab.ca/~abeinges/blah/rust-reuse-and-recycle/#monomorphization https://cglab.ca/~abeinges/blah/rust-reuse-and-recycle/#mono... From the article: * But if I want a single queue to be able to handle different tasks, then it's not clear how that could be done with monomorphization alone. That's why it's called "mono"morphization. It's all about taking abstract implementations and creating instances that do one thing. * Which was exactly what I was experimenting with: A single queue worker that can handle different cases. Honestly, it made Rust almost not worth it for me. Sadly, I was too deep to turn back so I wound up doing the whole thing in Rust. I have tons of copy-paste code. It is ugly and it is bothering me.
- twic 6y agoI've heard "monomorphisation" to refer to something a compiler does, but not something a programmer does. I think this is just "repetition"! The need for something to solve the problems inheritance solves has been known in Rust for a long time. Mostly it's been motivated by the need to implement the HTML DOM, which is fundamentally an inheritance hierarchy, in Servo. There's a longstanding RFC about it: https://github.com/rust-lang/rfcs/issues/349 https://github.com/rust-lang/rfcs/issues/349 Inheritance is one possible solution. There are others.
- ncmncm 6y agoThe original inspiration for the idea really has nothing whatever to do with its subsequent architectural role, nor with its value as a mechanism. As a performance optimization, inheritance demonstrated value at a time when performance improvements were at least three orders of magnitude more important than they are today. People here like to disparage OO and inheritance, but the distaste clearly is just a reaction to Java and its unfortunate lack of any other organizational mechanism, requiring abuse of the one feature for everything. In C++, it is sometimes directly useful, with or without a vtable, independent of "OO"; and even occasionally is exactly the right thing, explicitly as an OO tool. As one tool in the toolbox, it is simple, understandable, and causes no trouble unless badly abused. It is far from the only tool, so is not often the best choice, but probably most programs of any substance have one or two places where it is better used than not.
- cout 6y agoIME the problem with inheritance in C++ is that it is so easy programmers reach for it instinctively, even when another tool might be a better choice in the long term. I definitely use inheritance in my c++ code, but as I've grown (and as the language has grown) I've found myself using it less and less. Perhaps that's the way it's supposed to be.
- ncmncm 6y agoIt is a problem with teaching. If you are taught to imagine yourself as an OO programmer using an OO language to produce OO designs, you will shoehorn in OO mechanisms even where they fit poorly. C++ has other organizational tools for these cases, so that there is little natural temptation to abuse OO for them. People coming to C++ from impoverished languages like Java often fail to recognize bad habits they have internalized.
- zozbot234 6y agoImplementation inheritance is indeed problematic, no matter how it's used. The defining feature of implementation inheritance is that any code, relating to any class in the hierarchy, can rely on methods that may then be overridden in unpredictable ways further down in the hierarchy. If you don't need or expect this behavior, you can use composition and delegation instead - which come with a far simpler semantics and do a way better job of preserving modularity.
- cout 6y agoReference counting and a last resort GC? If you had asked me which language that describes, I would have said python. I had no idea it owed that design to simula.
- KMag 6y agoThis writeup is a bit unclear. It actually mentions two problems. The first is functions out-living stack-allocated arguments... this isn't a GC issue but a compiler issue that could be solved by escape analysis (or more powerful variants, like Rust's lifetime analysis). I guess that maybe they're implicitly talking about using a spaghetti stack instead of doing escape/lifetime analysis, and then needing the GC for the stack frame to support interior references. In any case, as they state the first problem, it's a compiler problem, not a GC problem. As they state in the article, inheritance doesn't solve this, and Simula just forbids by-name/by-reference arguments that are allocated in the caller's call frame. The second problem they describe is linked lists. A Java-like linked list means an extra level of indirection, where the list element holds a reference to the held object. For both performance and space reasons, they wanted to remove that layer of indirection. That meant either having their garbage collector support interior references (references to any field inside the object) or else making a reference to a linked list element be the same as a reference to the held object. They chose the second option, by way of making the element and the held object one and the same, using inheritance. A Simula linked list has as much indirection as a C++ std::list<T>, one fewer indirection than a java LinkedList<T>. They mention reference counting, but a mark-and-sweep, copying, or tricolor tracing collector would still have this same issue if it didn't support interior references. As sophisticated as Oracle's latest JVM is, I don't believe any of its several garbage collectors support interior references. Physically in the JVM's memory, LinkedList<T> really has an extra layer of indirection as compared to C++'s std::list<T>, and the JVM uses escape/lifetime analysis in order to stack-allocate some objects that would otherwise need to be heap-allocated.
- xxs 6y ago> A Java-like linked list means an extra level of indirection I guess you mean java.util.LinkedList that has virtually no use case that's not outperformed (both space and time) by another datastructure. Writing your own linked list with prev(+next) pointers ain't hard by it's nothing like C++ templates. The removal of layer of indirection does work in Java [pretty well] as well - like extending AtomicReference (or AtomicInteger), if you need a CAS and some other data, of course it comes with false sharing.
- idclip 6y agolife is not comfortable for those who get things given to them.
- dboreham 6y agoSeems unlikely to be true that inheritance was "invented" in Simula since it shows up all over the place outside of programming languages, for example in network protocol design.
- slver 6y agoInheritance is static composition. Everything we do statically is for two reasons: 1. Static invariants (not subject to runtime-defined conditions). 2. Performance (AOT compilers know more about the system and can elide more code and devirtualize more calls, etc.). I think the characterization of performance features as a "hack" is misleading. The article builds a bit of strawman, being dismissive of a performance feature for the fact it was for performance, then does a 180 in the Conclusion section saying "it's not bad" to look for performance. Did we forget inheritance vs. composition is also for performance? Honestly I've not seen such a strong characterization of inheritance as being purely semantic. If anything, today we see inheritance as the thing a junior dev reaches for first, in order to share code, because they don't know better. Sure, there's the "Cat extends Animal" shtick that refuses to die. But everyone refers to this kind of talk of inheritance ironically these days.
- CuriouslyC 6y agoI frequently hear people malign inheritance, and while it can obfuscate code in some circumstances, it can also produce code that is easily and clearly extendable. For example, a class with a static method that uses class properties to control behavior is cleaner than a function factory that takes a config object. Interface inheritance is also quite useful.
- mumblemumble 6y ago> it can also produce code that is easily and clearly extendable It can also produce code that is not easily and clearly extendable, if the behavior you're trying to extend is buried a layer or two further down the inheritance hierarchy. Unfortunately, due to the First Law of Kipple, the rate at which you run into this problem is proportional to the age of the code base. And so we grow frustrated with implementation inheritance. Other extensibility mechanisms probably have similar pitfalls, but they haven't been the dominant way of doing things for long enough to accumulate the same volume of clutter. Don Knuth has mentioned in a few interviews that he isn't so hot on code reuse, and prefers code that's designed to be easy to edit over code that's designed to be easy to extend. I'm starting to see some wisdom in that idea. With the one, being able to keep things tidy is a primary goal. With the other, eventually tidying becomes a frightening enterprise, because you have to avoid upsetting the inheritance hierarchies that are precariously balanced on top of the code you're trying to tidy up.
- slver 6y agoThis comment may get too meta, but technically everything we do, ever, is a performance hack.
- kortex 6y agoYou're not wrong. Aren't we all just manifestations of the Principle of Least Action?
- al2o3cr 6y agoThe GoF should have added a part 2, "Prefer thinking over mindlessly repeating slogans without context or nuance"
- tarkin2 6y agoInheritance in the OOP sense can be simply implemented in most languages without OOP. In Javascript: var o1 = { a: 1, b: 2, c: 3 } var o2 = { x: 1, y: 2, z: 3 } o1 = Object.assign(o1, o2) o1.z // 3 o1 has now inherited o2. I don't see much difference between this and classic untyped OOP. Edit: copying functions over, not values, is what I’m getting at. Values used for simplicity of example
- tsimionescu 6y agoWhile it's true that it's easy to implement inheritance in dynamic languages, it's not quite this easy. The most important feature of inheritance is the function override support / virtual dispatch, so that o1.foo() and o2.foo() can do different things, but o2.foo() can also access o1.foo() (by using something like super.foo() in Java or BaseClass::foo() in C++). Ideally this would also be optimized so that each object doesn't have to carry a function pointer for each member function (this is why virtual function tables are generally used).
- tarkin2 6y agoOverride support, no? function f() { function g() { return 1 } return { g } } function f1(f) { function g() { return f.g() + 1 } return { g } } f1(f())
- tsimionescu 6y agoSure, but that's a whole bunch more boilerplate than just .assign(), which was my point (it's easy, but not quite as easy as just calling assign). Especially since f needs to be written a certain way to be overridable, while in the previous example o1 was just an ordinary object. Edit: Not to mention that storing function pointers with each object is a huge waste of memory, versus storing one class pointer in each object, and having that class object store function pointers and parent class pointers. That way, you guarantee that each function pointer is stored exactly once, and each object has a single sizeof(pointer) of overhead (and even this can be improved with more complex implementations).
- 6y ago
- markroseman 6y agoOff topic, but one of my CS professors was part of the original Simula group. Had long sinced moved onto more formalized approaches, i.e., denotational semantics. On more than one occasion was heard to say "the only good object is a dead object."
- cowboyhassie 6y agoweird
- cannabis_sam 6y agoSubtyping with inheritance is kinda complicated from a theoretical perspective, so the trade-off in java-like languages seems to usually be to neuter the type system, by restricting type-inference and rely on nominal types specified by the programmer. After spending way to much time in Java, and OOP-style php and python, I just struggle to see the advantage of inheritance. It usually takes a long time to look through the object hierarchy to figure out where the behavior is really coming from.. and the loss of type inference outside of localized expression statements is devastating in comparison.