[HN Gopher] Inheritance was invented as a performance hack
       ___________________________________________________________________
        
       Inheritance was invented as a performance hack
        
       Author : signa11
       Score  : 227 points
       Date   : 2021-04-30 01:14 UTC (21 hours ago)
        
 (HTM) web link (catern.com)
 (TXT) w3m dump (catern.com)
        
       | markroseman wrote:
       | Off 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."
        
       | marcodiego wrote:
       | I was once one of the "luky 10.000"[0] about this:
       | https://www.snopes.com/fact-check/program-management/
       | 
       | [0] https://xkcd.com/1053/
        
         | BoorishBears wrote:
         | If it has a Snopes article debunking it, it's the inverse of
         | the lucky 10,000 since "everyone knows" the wrong fact
        
           | redis_mlc wrote:
           | The "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.
        
       | ncmncm wrote:
       | The 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 wrote:
         | IME 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 wrote:
           | It 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 wrote:
         | Implementation 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.
        
           | ncmncm wrote:
           | Implementation inheritance is absolutely no problem within a
           | project or component that entirely controls both base and
           | derived classes, where it amounts to, simply, a notational
           | convenience. It is not, then, a "good OO design"; but there
           | is nothing sacred about OO. Ultimately, any combination of
           | well-specified mechanisms may be correctly used to achieve an
           | elegant design, regardless of formal architectural
           | conventions. The reason a feature was introduced into a
           | language has no necessary connection to reasons for using it.
           | 
           | Implementation inheritance _is_ a problem when crossing
           | organizational boundaries, as reasonable changes upstream can
           | impact correctness downstream.
           | 
           | Ultimately, there is no substitute for taste. Substituting
           | fetishism produces unfortunate results.
        
       | TeeMassive wrote:
       | tl;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 wrote:
         | Doesn't Parent parent = Child() work because Parent's
         | constructor is run by implicitly casting ref-to-Child to ref-
         | to-Parent?
        
           | fauigerzigerk wrote:
           | I 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.
        
       | cout wrote:
       | Reference 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.
        
       | slver wrote:
       | This comment may get too meta, but technically everything we do,
       | ever, is a performance hack.
        
         | kortex wrote:
         | You're not wrong. Aren't we all just manifestations of the
         | Principle of Least Action?
        
       | cannabis_sam wrote:
       | Subtyping 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.
        
       | thewickedaxe wrote:
       | I'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]
        
         | rusk wrote:
         | I share your confusion to a degree. To me it always seemed that
         | the concept emerged from the work on Semantic Networks [0] by
         | Collins and Quinlan.
         | 
         | What's interesting is, looking at this now that these two
         | different aspects of what we nowadays consider to be a fairly
         | well defined computer science concept both seem to have emerged
         | around the same time.
         | 
         | Could it be C & Q were inspired by the Simula guys, or vice
         | versa maybe Simula adopted the term because the implementation
         | "resembled" the semantic definition. Or perhaps they
         | independently emerged from concepts that were popular at the
         | time!
         | 
         | [0] https://en.wikipedia.org/wiki/Semantic_network
        
         | username90 wrote:
         | Theory usually comes after practice. People create something
         | that works, then theorists formalizes it into a well defined
         | concept.
        
           | Arwill wrote:
           | I 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 wrote:
             | Many 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.
        
             | kitd wrote:
             | _I 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._
             | 
             | IIRC, there was impetus from Christopher Alexander's
             | architectural "pattern language", and the desire to do
             | something similar in software, which was a bit of an
             | unguided mess at the time.
        
               | Someone wrote:
               | Simula 67 is (surprise) from 1967, Smalltalk-76 from
               | 1976. Both supported objects and inheritance
               | (http://progopedia.com/language/simula-67/, http://worryd
               | ream.com/refs/Ingalls%20-%20The%20Smalltalk-76%...)
               | 
               | "A pattern language" is from 1977; its application to
               | programming from 1987 (http://c2.com/doc/oopsla87.html)
               | 
               | So, in the context of "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 think
               | this discussion is diverging a bit.
               | 
               | I would guess the idea of inheritance was already used in
               | some assembly programs. It would start with people using
               | structures that shared initial fields, and passing them
               | to functions that didn't care about the other fields
               | (functions handling intrusive linked lists, for example).
               | Passing a pointer to a function that _does_ know the
               | exact type of the fields would be the next step.
               | 
               | The next step would be to allow heterogenous collections
               | and use logic to discern between the various variants
               | (many lisps do that by going through an "is it an atom?,
               | is it a number? etc. chain, and likely did that years
               | before Simula 67 appeared)
               | 
               | Chances are somebody also embedded a function pointer or
               | two inside such objects, but I guess that would have come
               | fairly late, as a function pointer in each object would
               | have been expensive, memory-wise in the '60s and '70s.
               | 
               | Anyway, I think 'inheritance' indeed was
               | invented/discovered, but gradually, and without
               | explicitly naming it.
        
               | js8 wrote:
               | > which was a bit of an unguided mess at the time
               | 
               | Oh, come on. There was
               | https://en.wikipedia.org/wiki/Structured_program_theorem
               | and https://en.wikipedia.org/wiki/Modular_programming.
               | There was a discussion about functional programming, for
               | example famous John Backus article -
               | https://dl.acm.org/doi/pdf/10.1145/359576.359579.
        
               | kitd wrote:
               | I was really thinking lower-level, specifically OOP,
               | though yes I should've said it. OOP was going to save the
               | world but no one knew _how_ to design properly with it.
               | GoF patterns were a set of off-the-shelf tactical designs
               | intended to get you started in the right direction.
        
               | [deleted]
        
             | Tade0 wrote:
             | The other day I got into a heated debate with a C#
             | developer on the merits (or lack thereof) of adding an "I"
             | prefix to interfaces.
             | 
             | Turns out in C# there's no syntactic difference between
             | implementing an interface and inheritance, so _it makes
             | sense_ in C# to explicitly state that something is an
             | interface but, arguably, only there.
        
               | layer8 wrote:
               | The argument against "I" is that the _user_ (client code)
               | of an instance shouldn't have too care whether the type
               | of the reference is an interface or a class. Concerns of
               | the implementor shouldn't determine the naming visible to
               | the client code.
               | 
               | Of course, it's an established C# convention (and
               | inspired by the naming convention for COM interfaces) so
               | one better sticks with it, but I think the convention was
               | a bad choice for the reason stated above.
        
               | Tade0 wrote:
               | 100% agreed. My day job is in front-end development and
               | we unfortunately had this convention in a few projects.
               | 
               | Problem is, sometimes the are changes in how types are
               | composed and what used to be an interface can end up as a
               | type alias and vice versa.
        
               | dragonwriter wrote:
               | Yeah, IMV its a similar problem to the convention of
               | prefixing DB objects with T (table) or V (view), but not
               | as bad because refactoring between class and interface
               | should be rare.
        
             | rusk wrote:
             | > It was then later that some theoretical "usefulness" or
             | "elegance" was attached
             | 
             | This Chicken v Egg pondering is interesting. I mean, surely
             | the term "inheritance" was used in this instance because it
             | already resembled something that had been established in
             | theory?
        
           | nathias wrote:
           | idea > practice > formalization or idea > formalization >
           | practice
        
           | vincnetas wrote:
           | Just like some artists create art? First they do then they
           | explain.
        
         | morelisp wrote:
         | I would put OOP's solid theoretical foundations at the Liskov
         | Substitution Principle invented in ~1988. This seems relatively
         | late compared to OO's popularity. It was less than ~10 years
         | between "structured programming" and the structured programming
         | theorem (and lots of useful formal steps on the way), vs. 20+
         | for OOP and LSP (and at least at today's distance, LSP along
         | with Meyer's contemporary substitution principles seems more
         | like a sea change).
        
         | [deleted]
        
       | cowboyhassie wrote:
       | weird
        
       | dmix wrote:
       | Seems like a cool era to be working on computers. For lack of a
       | better phrase, the world was your oyster.
        
         | catern wrote:
         | Yes, 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 wrote:
       | Fine. 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.
        
         | dang wrote:
         | " _Please don 't sneer, including at the rest of the
         | community._"
         | 
         | https://news.ycombinator.com/newsguidelines.html
        
           | eternalban wrote:
           | dang, amazed at your parsing abilities at hn comment scale I
           | checked your profile to make sure you are not a machine and
           | read that passage you quoted which assures me you are a Human
           | being with exceptional taste. Beautiful.
           | 
           | For others who may be interested, poked around and found that
           | it is a quote from _The Street of Crocodiles and Other
           | Stories_ , by Bruno Schulz.
           | 
           | https://www.goodreads.com/book/show/1576188.The_Street_of_Cr.
           | ..
        
           | rectang wrote:
           | I take your critique, Dan, and in the future I'll try to find
           | different ways to make similar points.
           | 
           | I'm a bit deflated though, that now that this subthread has
           | been demoted and dropped to the bottom, the usual "everybody
           | knows inheritance sucks" comments are accumulating up at the
           | top. I really enjoyed having a discussion for once that tried
           | to suss out why inheritance, despite its flaws, was
           | historically so _semantically_ compelling.
        
             | dang wrote:
             | We downweight generic subthreads when we see them stuck at
             | the top of a page. I've downweighted that one now.
             | Obviously they're a problem (a big problem - maybe the
             | biggest - because the most generic tangent from any topic
             | is also the one that people are most familiar with, and
             | familiarity breeds both repetition and upvotes). But the
             | solution is not to have a generic tangent which is also a
             | flamewar! However, you already agreed on that point and I
             | don't mean to pile on.
        
         | SilverRed wrote:
         | What 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 wrote:
           | Many 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 wrote:
             | I'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.
        
           | FpUser wrote:
           | These 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".
        
           | [deleted]
        
         | bradrobertson wrote:
         | "Catching on" doesn't mean "is the right way to do things".
         | Code reuse is in fact a prime example of a misuse of
         | inheritance
        
           | ahartmetz wrote:
           | Correctly using a language feature often means using it for
           | more than one reason. Extending superclass code can be well
           | advised in case it also makes sense semantically. Now, I have
           | seen a "mixin" class that just served to provide one
           | relatively independent method, and I turned it into a free
           | function. Much better. In other cases, an extended class
           | works on the whole state of the object. In that case it
           | usually makes sense.
        
           | threatofrain wrote:
           | Why is code reuse your prime example for bad inheritance?
           | What should people do instead?
        
             | sabellito wrote:
             | In OO languages, inheritance gets the most bang for the
             | buck when used for polymorphism.
             | 
             | All code reuse can be expressed with composition and
             | delegation, bringing more flexibility, testability,
             | cohesion, decoupling, etc, etc, etc.
        
             | rectang wrote:
             | Interfaces with default implementations are better than
             | classes for a number of reasons, including the diamond
             | problem, and that as systems grow large they almost never
             | fit cleanly into an inheritance tree. Fat pointers with two
             | words, one for an interface vtable and one for the object,
             | work really well.
        
               | jcelerier wrote:
               | > Interfaces with default implementations are better than
               | classes for a number of reasons
               | 
               | it's literally the same thing in practice
               | 
               | > including the diamond problem,
               | 
               | the diamond problem has only ever been a "problem" in OOP
               | textbooks, in years and years of working on OO system I
               | have never saw it be a problem in practice
        
               | rectang wrote:
               | Do you know if the mechanism in described by Stroustrup
               | in 1989 for supporting multiple inheritance is actually
               | used by modern C++ compilers?
               | 
               | https://www.usenix.org/legacy/publications/compsystems/19
               | 89/...
               | 
               | The plan seems so complex that it makes complete sense to
               | me why languages would avoid multiple class inheritance
               | (where each class is afforded direct access to member
               | variables).
               | 
               | In contrast:
               | 
               | * With single inheritance, you don't have to resolve
               | different member variable layouts because there is only
               | one.
               | 
               | * With interface inheritance (with or without default
               | methods), interface methods don't get to access struct
               | members directly and know nothing about object memory
               | layout.
        
               | AnimalMuppet wrote:
               | In your view, how is "interfaces with default
               | implementations" different from "inheritance for code
               | reuse"? At a minimum it looks like a virtual function
               | with a default implementation in the base class. If a
               | derived class doesn't override it, isn't that code reuse?
        
               | rectang wrote:
               | > _In your view, how is "interfaces with default
               | implementations" different from "inheritance for code
               | reuse"?_
               | 
               | The latter is a more general classification?
               | 
               | A type which inherits a default method implementation
               | from an interface is an example of "inheritance for code
               | reuse".
               | 
               | A type which inherits a method implementation from a
               | parent class under classical OOP is also an example of
               | "inheritance for code reuse".
               | 
               | However, I argue that "interface inheritance with default
               | implementations" is superior to "classical OOP" because
               | it avoids tight coupling with memory layout, problems
               | with implementing multiple inheritance in classical OOP,
               | etc.
        
               | Spivak wrote:
               | Because interfaces don't confer a relationship between
               | implementations, only that they have some common method
               | signatures.
        
               | username90 wrote:
               | > Because interfaces don't confer a relationship between
               | implementations
               | 
               | Having the interface implement functions does exactly
               | that, so your point only applies for interfaces without
               | implementations.
        
               | loopz wrote:
               | Maybe he meant something similar to Go interfaces, but
               | they are abstract and coupled loosely with
               | implementations after the fact.
               | 
               | That or abstract class/pure interface. There's no need
               | for default implementations introducing assumptions in
               | code.
        
               | rectang wrote:
               | I believe that both Go and Rust use two-word fat pointers
               | for interface types. I was actually thinking of Rust
               | traits first and foremost; in Rust, traits can supply
               | default method implementations which invoke other methods
               | defined in the same trait.
               | 
               | While I take your point about introducing assumptions,
               | I'm reluctant to give up the convenience of default
               | implementations; they seem to present fewer problems than
               | classically inherited methods because shallow hierarchies
               | are more common with interfaces than with classical
               | inheritance, and because default interface methods cannot
               | directly access member variables because they do not know
               | about object layout -- unlike methods inherited from a
               | parent class under classical inheritance.
        
               | ratww wrote:
               | "Interfaces with default implementations" are a thing in
               | Java and in some other languages, but most people don't
               | know about them:
               | 
               | https://docs.oracle.com/javase/tutorial/java/IandI/defaul
               | tme...
        
               | rectang wrote:
               | I knew that Java added "default methods" to its
               | interfaces a few years ago, but wasn't sure whether Java
               | uses "fat pointers" like Rust and Go.
        
             | loopz wrote:
             | Inheritance is just a tool to mostly avoid. It tightly
             | couples different class implementations. So will make later
             | refactors error-prone and tend to scatter logic. It's
             | misunderstanding OO as messaging between objects.
             | 
             | Using composition or lay out data differently may yield
             | designs with more desirable properties. There's no one
             | answer and it depends.
        
           | pansa2 wrote:
           | > _Code reuse is in fact a prime example of a misuse of
           | inheritance_
           | 
           | Does that depend on how inheritance works in a particular
           | language? For example, in Python it's widely believed that
           | inheritance is simply "a tool for code reuse" [0].
           | 
           | [0] https://youtu.be/EiOglTERPEo?t=190
        
           | signa11 wrote:
           | yup, totally agree. imho, composition is the right (only ?)
           | way to code reuse.
        
           | hota_mazi wrote:
           | It's the main reason why inheritance became so popular and so
           | useful.
           | 
           | Specialization remains a very common design pattern that is
           | incredibly useful and trivially and intuitively solved with
           | inheritance. No other programming concept (HKT, ad hoc
           | polymorphism, functional programming, etc...) comes close to
           | its elegance.
        
             | ratww wrote:
             | Code-reuse via "Implementation Inheritance" is completely
             | unnecessary for specialisation or polymorphism.
             | 
             | When someone says that "inheritance is bad for code reuse"
             | they're not talking about interfaces, or using inheritance
             | for polymorphism. They're strictly talking about sharing
             | code using implementation inheritance, which is the thing
             | that has been widely criticised for more than 30 years now.
             | 
             | One can argue that even the "Template Method Pattern"
             | doesn't fall into "implementation inheritance", since the
             | implementation lives in the subclass.
             | 
             | If you read the posts, discussion is way more nuanced than
             | "inheritance bad vs inheritance good".
        
               | hota_mazi wrote:
               | Reusing implementation is the point.
               | 
               | Here is more specifically what I meant with my comment
               | about specialization above. There is a class with four
               | methods, three of which are exactly what you need but the
               | fourth one, you need to modify.
               | 
               | Solving this with inheritance is trivial (extend and
               | override).
               | 
               | Solving this with any other paradigm is... much harder
               | and requires a lot more boilerplate.
        
               | aflag wrote:
               | In functional programming you'd just create a new
               | function that takes a value of the same type of those
               | other 3 methods.
               | 
               | But anyway, creating a new method without changing any of
               | the methods of the super class I think it's generally ok.
               | The problems arise from modifying methods that the super
               | class already implemented.
        
               | hota_mazi wrote:
               | But code that uses the old function wouldn't magically
               | start invoking your new function instead, something that
               | inheritance and polymorphism give you for free.
        
               | aflag wrote:
               | Nothing will magically start calling your new function.
               | If you are defining a new function that didn't exist
               | before, then you'll have to actively call it somewhere.
               | What you're describing instead is overriding an inherited
               | function. However, that is full of pitfalls, I would not
               | call that "for free" by any means. There are example of
               | the problems in this very thread. Anyway, that's distinct
               | from polymorphsim, which is present in functional
               | programming.
               | 
               | You may be missing the point that's being made, though.
               | No one is arguing against interfaces, but overriding
               | concrete methods from a concrete class. Those need to be
               | well thought out as extension points for you to have any
               | chance of having stable software. Not quite for free.
        
             | rectang wrote:
             | > _Specialization remains a very common design pattern that
             | is incredibly useful and trivially and intuitively solved
             | with inheritance._
             | 
             | Beautifully put. That matches up with my own experience
             | learning classical OOP, even if I'm now comfortable with
             | other models.
        
           | Varriount wrote:
           | How is it a misuse? And what are the alternatives?
           | 
           | To be clear, I do agree with your assertion, however I'm
           | having a hard time actually putting "how/why" into words.
        
             | Spivak wrote:
             | It's a misuse because (if you really are _only_ using it
             | for code reuse) it's creating an essential relationship
             | between different objects that is actually incidental.
             | 
             | A list containing two different subclasses of your
             | avoiding-copy-paste base class is now most likely a logical
             | error.
        
               | Dylan16807 wrote:
               | > A list containing two different subclasses of your
               | avoiding-copy-paste base class is now most likely a
               | logical error.
               | 
               | So don't allow those? Supporting inheritance doesn't mean
               | you have to support heterogeneous lists.
        
               | [deleted]
        
               | username90 wrote:
               | You could just as well argue that there should never be
               | any code reuse ever, code reuse always couples objects,
               | code reuse should never be done if the two different
               | parts doesn't have the same purpose. So the same rules
               | for all code reuse also applies for inheritance, if you
               | follow them then there are no problems using inheritance.
               | If you have a set of data kinds which all needs to
               | implement the same fields that has the same purposes then
               | having a list of that parent class doesn't lead to bugs.
        
               | throwaway894345 wrote:
               | I don't think it's true that reuse means tight coupling.
               | With composition you can swap out the composed object at
               | runtime and as far as compile time coupling is concerned
               | you can embed an interface to decouple the outer object
               | from the inner object.
        
               | username90 wrote:
               | All code reuse creates coupling on some level. Creating
               | more abstractions in between reduces coupling but creates
               | code bloat. There is a trade off.
               | 
               | For example, if you call the same function from many
               | locations those locations are now coupled since if you
               | change the function you change all of those locations
               | behaviour. Many times that is desirable, in which case it
               | is good coupling. The exact same rule applies to
               | inheritance.
        
               | throwaway894345 wrote:
               | Can you elaborate a bit? How is there coupling between a
               | thing which uses an interface and a thing which
               | implements the interface of the two don't know about each
               | other? Especially if the interface is implicit a la Go
               | interfaces or structural subtyping?
        
               | username90 wrote:
               | Every class implementing the interface needs to follow
               | the same interface contract, that is coupling. If you
               | want to change the interface contract for one of them you
               | have to go and change it for all of them or you have
               | buggy interfaces. Every time you create an interface you
               | create a contract for it, if you have the same discipline
               | with inheritance then inheritance isn't an issue.
               | 
               | The only problem with inheritance is that people can use
               | it for classes that weren't written to be base classes,
               | and therefore a ton of bad programmers use it for code
               | reuse without thinking about the contract it is supposed
               | to implement at all. With that in mind, allowing
               | inheritance only for abstract classes isn't an issue at
               | all.
        
               | findjashua wrote:
               | how is that different from a list containing 2
               | implementations of an interface?
        
               | foo_barrio wrote:
               | There is no state to track. If there is, 99% composition
               | has worked out way better for me in the long run since I
               | don't have this really strong coupling between two
               | objects.
        
               | username90 wrote:
               | So you don't have a need for code reuse then since you
               | don't have many types with the same base structure, just
               | say that instead of saying that it is bad.
        
               | catlifeonmars wrote:
               | There are often more straightforward ways to reuse code
               | than to use inheritance. ~Two~ three advantages of
               | composition for code reuse:
               | 
               | 1. Composition is often more explicit than inheritance.
               | Inheritance is overly magic.
               | 
               | 2. Composition avoids incidental coupling of methods.
               | With inheritance this is unavoidable.
               | 
               | 3. Composition is more difficult to misuse. Both
               | composition and inheritance have their purposes. In the
               | wild, I've seen inheritance misapplied much more often
               | than I have seen with composition.
        
         | galangalalgol wrote:
         | The best practices I've read mostly say that inheritance should
         | be limited to one non-interface parent. I.E. obly one parent
         | with an invariant. For everything else composition. That mostly
         | lines up with my experience, but I mostly deal with problem
         | spaces that aren't terribly OO like DSP.
        
         | foerbert wrote:
         | Is this ragging on inheritance? I don't see much in the way of
         | value judgements mentioned. The closest is an assertion that
         | doing things for performance is not bad, which seems more like
         | a way of heading off comments like yours than anything else.
         | 
         | It's a bit of interesting history. I don't know why it would
         | get a "Fine. But unrelated stuff" response.
        
           | rectang wrote:
           | I suspect that many will read the title as derogatory, even
           | if the post is not a firebreather. Also, from the conclusion:
           | 
           | > _Personally, for code reuse and extensibility, I prefer
           | composition and modules._
        
           | FpUser wrote:
           | Every once in a while one concept goes out of fashion and
           | another one becomes current fetish. Often the new is actually
           | an old one and sometime is masquerading under new name. And
           | then you have army of prophets that are here to show you "the
           | right way". Rinse and repeat. Personally I read and try
           | various things and use whatever I think is appropriate for
           | given situation. Do not really give a fuck about what is the
           | official point of view on subject at the moment. It changes
           | of course if doing job for client and client insists on
           | things done in particular way. Luckily I mostly design and
           | build complete products so clients mostly care about the end
           | result and do not interfere in the internal plumbing matters.
        
         | throwaway894345 wrote:
         | My roots are in OO but for the life of me I could never figure
         | out why people found inheritance intuitive beyond some
         | automatic delegation (like Go's struct embedding). I certainly
         | don't miss the guesswork of trying to figure out which class's
         | virtual method is being executed out of the dozen in the
         | hierarchy (especially when one virtual method is calling other
         | virtual methods).
        
           | junon wrote:
           | Because some people understand "is a" better than "has a".
        
             | squiggleblaz wrote:
             | That's just a restatement of the original problem.
             | Inheritance just is "is a" and composition just is "has a",
             | so if a person doesn't understand why someone would find
             | inheritance more intuititve than composition, they wouldn't
             | understand why a person finds "is a" more intuitive than
             | "has a".
        
       | anonytrary wrote:
       | Pretty 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. ;)
        
         | cghendrix wrote:
         | How to improve your TC with this one trick (other relatives
         | hate this)
        
         | Shorel wrote:
         | I was thinking the same thing.
        
         | shoto_io wrote:
         | I first thought it was about genes and evolution...
        
         | take3 wrote:
         | Monetary inheritance is likely bad for the species from an
         | evolutionary standpoint.
         | 
         | For example, if a "manipulative anti-social" gene was increased
         | in prevalence because it maximized net worth.
         | 
         | The problem here is a common issue in game theory, optimization
         | without regard for process. Your inheritance doesn't care how
         | it was acquired.
         | 
         | Sure this could be offset for positive genes, but that's making
         | one big assumption.
         | 
         | The assumption that capitalism is the _only_ reward function
         | for our species. That is, what other forms of inheritance
         | reproductively compete with money?
        
         | bubblethink wrote:
         | Heh. Have we all been trained to expect increasingly medium-
         | style lifestyle content on HN ? This title was a nice Rorschach
         | test.
        
         | Jiocus wrote:
         | I read the title as "new hypothesis about evolutionary theory
         | and gene inheritance".
         | 
         | This is why I'm not a biologist (or OOP fan). :)
        
           | anonytrary wrote:
           | Omg I can't believe I didn't see it this way first, this is
           | quite some title!
        
         | dudeinjapan wrote:
         | I 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
        
           | gher-shyu3i wrote:
           | What's wrong with someone wanting to provide for his
           | offspring?
        
             | carlhjerpe wrote:
             | Ted Kazinsky was talking about it in his manifesto.
             | 
             | 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.
        
               | gher-shyu3i wrote:
               | An atheistic/secular lifestyle tends to result in such
               | outcomes.
        
               | cto_of_antifa wrote:
               | yikes. why?
        
               | jokethrowaway wrote:
               | That'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 wrote:
               | Exactly. There are many rich people who have properly
               | raised their children.
        
           | take3 wrote:
           | If you're "not able to spend it in a lifetime..."
           | 
           | Then you're selfish and no good for the money anyway.
           | 
           | One can only buy so many jets and sports teams, sure.
           | 
           | But, if society endows you with that sort of reward, it
           | becomes your responsibility to move society forward with that
           | blessing.
           | 
           | Anything less is exactly why we live in Trump world and not
           | some sci-fi future we all imagined as kids.
        
             | whywhywhywhy wrote:
             | Life is more than just self indulgences, I don't actually
             | see a point in working or contributing to society at all if
             | I can't build a foundation that will benefit my children.
             | I'm certainly not working so I can drive a fancy car or
             | whatever.
             | 
             | Honestly not sure I'd even bother getting out of bed most
             | days if the future of my children was just in the hands of
             | the state, what would be the point.
        
               | take3 wrote:
               | Sounds like you may be depressed. My mom would tell us of
               | how, similarly, us kids would be the only thing that
               | helped her get through periods of depression.
               | 
               | Life itself, even for those of us without children, is an
               | incredible experience.
        
               | whywhywhywhy wrote:
               | I just work myself to my limits but I do it for a reason,
               | if that reason isn't there why not just be comfy instead?
        
           | jokethrowaway wrote:
           | Inheritance is a natural phenomenon.
           | 
           | If all my money were stolen when I die (let's say, 100%
           | inheritance tax), I wouldn't have much incentive to grow my
           | business more once my business makes more money than I can
           | possibly spend in a lifetime.
           | 
           | Sure, some people may still do it because they love working
           | or because they love providing for their society.
           | 
           | The main reason I want to accumulate money, is so I can offer
           | them a job instead of them going through the corporate
           | bullcrap I had to do when needing some starting capital.
           | 
           | Bigger businesses are capable of bigger bets (think Elon
           | Musk) which benefit everyone, removing an economic incentive
           | doesn't sound wise for innovation and growth.
        
             | amelius wrote:
             | Also, there are probably numerous ways to avoid inheritance
             | tax, so a 100% inheritance tax wouldn't make much sense
             | anyway (even if I agree ideologically with such a tax).
        
             | bb101 wrote:
             | Your business doesn't operate in a vacuum. It is licensed
             | and supported by society. The same society that benefits
             | from inheritance redistribution.
        
               | dahfizz wrote:
               | By that logic, why let the business owner make any money
               | at all? It's really society's business after all.
        
               | [deleted]
        
               | alexilliamson wrote:
               | The business owner won't exist if that person doesn't
               | have the potential to make some money. There is a
               | surprisingly large amount of possibilities between 0% and
               | 100% inheritances tax rate.
        
               | Thursday032521 wrote:
               | Correct- it should go to the workers.
        
             | tnzm wrote:
             | >wouldn't have much incentive to grow my business more once
             | my business makes more money than I can possibly spend in a
             | lifetime.
             | 
             | >going through the corporate bullcrap I had to do when
             | needing some starting capital.
             | 
             | The corporate bullcrap you had to go through is a direct
             | consequence of someone growing their business beyond what
             | they could spend in a lifetime.
        
             | dragonwriter wrote:
             | > Inheritance is a natural phenomenon.
             | 
             | Biological inheritance, property inheritance, however, is
             | not.
             | 
             | > If all my money were stolen when I die (let's say, 100%
             | inheritance tax), I wouldn't have much incentive to grow my
             | business more once my business makes more money than I can
             | possibly spend in a lifetime.
             | 
             | Even if one agrees that that is true, that's not an
             | argument that inheritance is natural, that's an argument
             | that it is a feature of society chosen to incentivise
             | people to accumulate wealth surplus to their personal
             | needs.
        
               | achenatx wrote:
               | property inheritance seems like it is also a natural
               | phenomenon. For example animals can "inherit" territory.
               | 
               | https://ideas.ted.com/humans-arent-the-only-ones-that-
               | help-o...
        
             | HeckFeck wrote:
             | It's also an extension of basic property rights. An owner
             | of property may will it to whomever he pleases on the event
             | of his passing. It's no different from simply gifting
             | property while alive.
        
               | wolfram74 wrote:
               | You imply an interesting question: Do the dead have all
               | the same rights as the living? I'm leaning towards no. So
               | I think it's a little different from gifting something
               | while alive, for starters, a gift while alive is
               | typically at personal cost unless you no longer have any
               | use for the item. Once dead you have no use for any
               | items, as you're definitionally out of the "able to use
               | things" population.
        
               | HeckFeck wrote:
               | Personally, most gifts I've given or received have been
               | items which I didn't need and not at any great cost. I
               | still gave them to a person of my choosing. So when dead
               | and in need of nothing, one's wishes to give to a
               | particular person, charity, or even hypothetically anyone
               | should be respected.
               | 
               | If John owns an item, he can say what happens it. So if
               | he says to an notarised person 'when I die X becomes
               | property of my son James' then that's simply respecting
               | his wishes over his property.
               | 
               | Is it much different if John choose to give something
               | away moments from death, or if he asks someone else to
               | arrange the giving on his behalf after death?
               | 
               | We respect the wishes of the dead because it's part of
               | the continuance of life and civilisation (which is itself
               | fundamentally the store and passing down of value, I.e.
               | inheritance). The same reason we'd honour someone who
               | died for freedom, or honour the funeral wishes of a
               | family member. Their wishes are worth much to everyone
               | who knew them. The same should apply to personal
               | property.
               | 
               | I think I'm stabbing in the right direction. A better
               | answer might come from a better read or more
               | philosophical mind than mine.
        
         | taneq wrote:
         | > "inheriting money is a way avoid being a high performer",
         | which happens to be true
         | 
         | By what metric? Or do you mean "a way to avoid having to be a
         | high performer in order to achieve good results"? Isn't the
         | whole beef with wealth inequality that given otherwise equal
         | luck and faculties, a person who starts with more capital will
         | gain more capital?
        
           | cdogl wrote:
           | The two aren't mutually exclusive. The children of the very
           | wealthy often (usually?) share their parents' economic
           | advantage, thus perpetuating the inequality that originated
           | due to economic factors. But that doesn't mean they're driven
           | or that they'll develop the same qualities that drove their
           | parents to be successful.
        
             | taneq wrote:
             | Well yeah, that's why I asked.
        
             | andrepd wrote:
             | > But that doesn't mean they're driven or that they'll
             | develop the same qualities that drove their parents to be
             | successful.
             | 
             | That's the point: a leasurely trust-fund baby will likely
             | be more "successful" than a hardworking and "driven" poor
             | person.
        
         | ck425 wrote:
         | I thought the same and tbh was quite excited to read about
         | that. Less interested in OOP.
        
         | marderfarker2 wrote:
         | Huh, I was thinking along the lines of inheriting genes.
        
       | colineartheta wrote:
       | Inheritance 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 wrote:
         | This article discusses object/class inheritance in programming
         | languages, not wealth inheritance between people.
        
           | colineartheta wrote:
           | There's a difference?
        
             | mashpoe wrote:
             | In 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.
        
       | al2o3cr wrote:
       | The GoF should have added a part 2, "Prefer thinking over
       | mindlessly repeating slogans without context or nuance"
        
       | KMag wrote:
       | This 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.
        
         | ilammy wrote:
         | > _a compiler issue that could be solved by escape analysis_
         | 
         | It could be solved _today_ , not in the sixties when Simula has
         | been designed. It's fascinating how much scientific progress
         | we're taking for granted, assuming that is has been that way
         | forever.
         | 
         | > _A Simula linked list has as much indirection as a C++
         | std::list <T>_
         | 
         | Barring the same comment on the progress in generic types,
         | intrusive lists are on a bit different scale. The difference
         | between a T which can be a part of intrusive list and List<T>
         | is in how T gets into a list. For intrusive list the links are
         | already in your T. If you have an element and want to add it to
         | the list then it's a bunch of pointer assignments. For non-
         | intrusive list you have to allocate a new list node, and maybe
         | even move your element there.
        
         | xxs wrote:
         | > 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.
        
           | KMag wrote:
           | Yes, I mean java.util.LinkedList. Obviously, one can do
           | manual writing of Java (or even use m4 macros or hijack the C
           | preprocessor... the preprocessor doesn't know anything about
           | C beyond tokenization) for anything C++ templates can do.
           | It's just tedious and potentially error-prone. Here's hoping
           | Java eventually gets reified generics.
        
             | MaxBarraclough wrote:
             | > can do manual writing of Java (or even use m4 macros or
             | hijack the C preprocessor... the preprocessor doesn't know
             | anything about C beyond tokenization) for anything C++
             | templates can do
             | 
             | Sounds broadly true, but I'm not sure it's precisely right.
             | How could _std::is_same_ be implemented?
        
               | KMag wrote:
               | > How could std::is_same be implemented?
               | 
               | You would perform manual static context-sensitive text
               | expansion, just like the C++ template expander does. In
               | this case, you end up just manually expanding it all the
               | way out to a true or false by hand.
               | 
               | I'm just saying that the C++ template expander only
               | performs static context-sensitive text replacements,
               | ending in valid template-free C++ code. The template
               | engine doesn't introduce any magic that can't be done
               | more tediously in template-free C++ by hand, so you can
               | do the same tricks in Java by hand.
               | 
               | Now, Java doesn't have non-primitive value types, so
               | you'd have to manually flatten some classes in order to
               | get the same layout without the class boundary forcing an
               | indirection. Java also doesn't allow interior references
               | while C++ absolutely does, but that's outside the C++
               | template expander / template sub-language.
        
               | [deleted]
        
               | MaxBarraclough wrote:
               | > You would perform manual static context-sensitive text
               | expansion, just like the C++ template expander does.
               | 
               | Sure, if you move all your code into your new language
               | that transpiles to C, but that was my point, the lack of
               | language integration means there's plenty it can't do
               | that C++ templates can do.
               | 
               | An example from [0]:
               | std::is_same_v<int, std::int64_t>
               | 
               | In C, I don't know that there's any way to determine
               | whether _int_ and _int64_t_ are the same type.
               | 
               | [0] https://en.cppreference.com/w/cpp/types/is_same
        
               | gpderetta wrote:
               | C++ templates cannot be implemented purely by text
               | replacement as most of it is type driven.
               | 
               | It is true that you can do anything that templates do by
               | hand, but that's true for any language construct (at the
               | limit you could write asm).
        
               | KMag wrote:
               | > C++ templates cannot be implemented purely by text
               | replacement as most of it is type driven.
               | 
               | The static types (and constant values in some cases) are
               | the context. That's the "context-sensitive" part of
               | "context-sensitive expansion".
               | 
               | > It is true that you can do anything that templates do
               | by hand, but that's true for any language construct (at
               | the limit you could write asm).
               | 
               | Yes. That's my point. I was replying to someone saying
               | that they could remove a layer of indirection from a
               | linked list by writing a custom data structure that had
               | the next and previous references, and I was pointing out
               | that they're essentially running C++ template expansion
               | by hand. There's nothing surprising there.
        
             | xxs wrote:
             | Obviously you can do
             | 
             | public class Node<T>{ Node prev, next; //stuff comes here
             | .... }
             | 
             | Then extend and have the overall code that deals with
             | modifying the datastructure, but it's overall ugly.
             | Personally I have written enough datastructures where
             | linking nodes is useful. For example: red/black tree +
             | insert order 'next' makes for a decent implementation of a
             | moving median. Yet, that's not what almost any developer
             | would do normally.
        
               | KMag wrote:
               | But isn't that just what you were suggesting in the GP
               | post in order to get rid of a layer of indirection?
        
               | xxs wrote:
               | Perhaps, most of the time I have not created a general
               | use data structure - just highly specialized ones.
               | Extending would allow no references/indirection but it's
               | rather ugly and it has to provide another function to
               | instantiate (I'd consider reflection, i.e.
               | Class.newInstance() not to be a good way to handle the
               | case).
        
       | idclip wrote:
       | life is not comfortable for those who get things given to them.
        
       | fiddlerwoaroof wrote:
       | I 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
        
         | rusk wrote:
         | I think you might be looking for this
         | https://en.wikipedia.org/wiki/Semantic_network
         | 
         | I believe it was Collins and Quinlan did the seminal work on
         | the conceptual side, but what's interesting is it seems to
         | emerge around the same time ... considering how inextricably
         | bound the two are in computer science it's an interesting
         | chicken/egg problem!
        
         | KineticLensman wrote:
         | > I've gotten the impression that knowledge representation
         | research (things like frame systems) was a semi-independent
         | influence on the design of OO systems.
         | 
         | Definitely! See [0]. Personally, I came across Frame-based
         | knowledge representations in the mid-1980. For reasons that
         | were frankly insane, I was trying to code a frame-based
         | knowledge representation in distinctly non-OO K&R C as part of
         | a collaborative university research project. I moved into
         | industry in 1988 and used OO for the first time (the Common
         | Lisp Object System) and finally started with C++ in around
         | 1993. Obviously this was my personal career trajectory but I
         | encountered knowledge representation research before mainstream
         | OO languages.
         | 
         | [0]
         | https://en.wikipedia.org/wiki/Frame_(artificial_intelligence...
        
         | mycall wrote:
         | c2 has some interesting thoughts on the topic.
         | 
         | https://wiki.c2.com/?KnowledgeRepresentations
         | 
         | https://wiki.c2.com/?TypeInferenceStory
         | 
         | https://www.cin.ufpe.br/~mtcfa/files/in1122/Knowledge%20Repr...
        
           | dleslie wrote:
           | How is it that c2 has remained so quality?
        
             | fiddlerwoaroof wrote:
             | It turned write-only before the Eternal September of
             | wikispam set in
        
               | zeckalpha wrote:
               | Write-only is a great place for spam... no need to read
               | it!
        
               | fiddlerwoaroof wrote:
               | Oops, I'm going to leave it because it's funny.
        
               | chriswarbo wrote:
               | https://en.wikipedia.org/wiki/Write-only_memory_(joke)
        
               | hinkley wrote:
               | I 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 wrote:
               | I'm not really sure, I think a fork and merge-request
               | system might be useful? Or requiring registration for all
               | editors. The big issue is figuring out how to reduce the
               | moderation burden to a reasonable amount.
        
             | Banana699 wrote:
             | It didnt? there is a _lot_ of "no u" arguments on C2
        
         | jhgb wrote:
         | > discussions of OOP and inheritance often miss the influence
         | of Knowledge Representation on OOP
         | 
         | SICP doesn't. :) Which is why it's such a great book, of
         | course.
        
         | jasode wrote:
         | _> discussions of OOP and inheritance often miss the influence
         | of Knowledge Representation on OOP: I'm not entirely clear of
         | the history myself, _
         | 
         | Fyi... The "knowledge representation" aspect of OOP inheritance
         | is emphasized by computer science professor Andrew P. Black. He
         | wrote a long paper[1] about it and also has a video[2]. It's
         | unfortunate that Black's alternative perspective (which he
         | shared with Philip Wadler) is not discussed as often as
         | "Object-Oriented Programming is Bad"[3] videos.
         | 
         | His paper is a long read (over an hour) that covers the
         | _intellectual history_ of OOP starting with Simula /Smalltalk.
         | The following is an excerpt from https://www.sciencedirect.com/
         | science/article/pii/S089054011...:
         | 
         |  _Most of us can grasp new ideas most easily if we are first
         | introduced to one or two concrete instances, and are then shown
         | the generalisation. To put this another way: people learn best
         | from examples. So we might first solve a problem for , and then
         | make the changes necessary for 4 to approach infinity. [...]
         | 
         | To illustrate the power of inheritance to make complex
         | abstractions easier to understand, let's look at a case study
         | from the functional programming literature. In Programming
         | Erlang [19, Chapter 16], Armstrong introduces the OTP (Open
         | Telecom Platform) generic server. [...]
         | 
         | To make sure that the message about the way that the OTP server
         | works does sink in, Armstrong presents us with four little
         | servers ... each slightly different from the last. server1 runs
         | some supplied code in a server, where it responds to remote
         | requests; server2 makes each remote request an atomic
         | transaction; server3 adds hot code swapping, and server4
         | provides both transactions and hot code swapping.
         | 
         | Each of these "four little servers" is self-contained: server4,
         | for example, makes no reference to any of the preceding three
         | servers.
         | 
         | Why does Armstrong describe the OTP sever in this way, rather
         | than just presenting server4, which is his destination? Because
         | he views server4 as too complicated for the reader to
         | understand in one go. Something as complex as server4 needs to
         | be introduced step-by-step. However, his language, lacking
         | inheritance (and higher-order functions) does not provide a way
         | of capturing this stepwise development.
         | 
         | In an effort to understand the OTP server, I coded it up in
         | Smalltalk. [...] First I translated server1 into Smalltalk; I
         | called it BasicServer, and it had three methods and 21 lines of
         | code. Then I needed to test my code, so I wrote a name server
         | plug-in for BasicServer, set up unit tests, and made them pass.
         | In the process, as Forsythe had predicted, I gained a much
         | clearer understanding of how Armstrong's server1 worked. Thus
         | equipped, I was able to implement TransactionServer by
         | subclassing BasicServer, and HotSwapServer by subclassing
         | TransactionServer, bringing me to something that was equivalent
         | to Armstrong's server4 in two steps, each of which added just
         | one new concern.
         | 
         | Once I was done, I discussed what I had learned with Phil
         | Wadler. Wadler has been thinking deeply, and writing, about
         | functional programming since the 1980s; amongst other
         | influential articles he has authored "The Essence of Functional
         | Programming" [21] and "Comprehending Monads" [22]. His first
         | reaction was that the Erlang version was simpler because it
         | could be described in straight-line code with no need for
         | inheritance. I pointed out that I could refactor the Smalltalk
         | version to remove the inheritance, simply by copying down all
         | of the methods from the superclasses into HotSwapServer, but
         | that doing so would be a bad idea. Why? Because the series of
         | three classes, each building on its superclass, explained how
         | HotSwapServer worked in much the same way that Armstrong
         | explained it in Chapter 16 of Programming Erlang. This was an
         | "ah-ha moment" for Phil.
         | 
         | To summarise: most people understand complex ideas
         | incrementally, by starting with a simple concrete example, and
         | then taking a series of generalisation steps. A program that
         | uses inheritance can explain complex behaviour incrementally,
         | by starting with a simple class or object, and then
         | generalising it in a series of inheritance steps. The power of
         | inheritance is that it enables us to organise our programs
         | incrementally, that is, in a fashion that corresponds to the
         | way that most people think._
         | 
         | [1]
         | https://www.sciencedirect.com/science/article/pii/S089054011...
         | 
         | [2] https://www.youtube.com/watch?v=Rmg_trKnanU
         | 
         | [3] https://news.ycombinator.com/item?id=19407599
        
       | valenterry wrote:
       | After 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.
        
         | valenterry wrote:
         | I am being downvoted and I would really like to hear from
         | people why that is - e.g. if I said something that is not
         | correct, please point it out so that I can learn from it.
        
         | judofyr wrote:
         | > 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.
         | 
         | I'm not sure what's so special about overwriting methods here.
         | There are many cases where semantics can break as a dependency
         | changes. One example would be whether a callback is executed on
         | the same thread (or event-loop tick) or in the background. A
         | function might assume that the callback should be pure and thus
         | in a later version switch where it's being executed. If your
         | code depended on it being executed in a certain way, then it'll
         | now be broken. The problem here is that there were certain
         | _constraints_ of the function which you weren 't aware of
         | (possibly because it wasn't documented).
         | 
         | A base class will have a similar constraints that you, as a
         | subclasser, must be aware of.
         | 
         | Your stack example is also a good example of where inheritance
         | is actually useful:                   class CountingStack(...)
         | extends Stack {            override def push(element) returns
         | nothing =                count += 1; super.push(element)
         | override def pop() returns element =                count -= 1;
         | super.pop(element)         }
         | 
         | Assuming that the Stack class specifies that all modifications
         | will go through the push and pop methods, this ensures that the
         | count is maintained regardless of how many other utility
         | methods that exists on Stack (i.e. clear()). You don't have to
         | extend the CountingStack with extra methods as the Stack
         | methods gain more methods.
        
           | nicoburns wrote:
           | > There are many cases where semantics can break as a
           | dependency changes. One example would be whether a callback
           | is executed on the same thread (or event-loop tick) or in the
           | background. A function might assume that the callback should
           | be pure and thus in a later version switch where it's being
           | executed.
           | 
           | Rust's Send and Sync traits solve this particular problem by
           | putting the thread-send safety in the type signature. If the
           | library changes to use a background thread and you have
           | passed it a non-threadsafe closure then you'll get a compile
           | error.
        
           | valenterry wrote:
           | Very good point actually.
           | 
           | 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:                   class Stack(...)
           | extends Stack {            def push(element) returns
           | IO[nothing] = ...                        def pop() returns
           | IO[element] = ...         }
           | 
           | 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:                   class
           | CountingStack(underlyingStack) extends Stack {
           | override def push(element) returns IO[nothing] =
           | underlyingStack.push(element).andThen(nothing => count += 1;
           | return nothing)                        override def pop()
           | returns element =
           | underlyingStack.pop(element).andThen(element => count += 1;
           | return element)         }
           | 
           | 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).
        
             | thu2111 wrote:
             | 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.
        
           | zozbot234 wrote:
           | > I'm not sure what's so special about overwriting methods
           | here. There are many cases where semantics can break as a
           | dependency changes.
           | 
           | The problem with implementation inheritance specifically is
           | that there is no single responsibility for establishing this
           | semantics. _Every_ method call on overridable methods incurs
           | a dispatch step involving the whole of an arbitrarily
           | extensible inheritance hierarchy, and every method can be
           | called by an unknown extent of user code. You just can 't
           | give a proper semantics to it without looking at the whole-
           | program level, which is antithetical to modularity.
        
         | chriswarbo wrote:
         | An example I often link to is
         | http://okmij.org/ftp/Computation/Subtyping which uses a
         | similarly simple example (bags and sets) to show that
         | supposedly "internal" changes can still break subclasses.
        
         | toomanybeersies wrote:
         | Breaking the implementation of CountingStack by changing the
         | implementation of Stack is the kind of stuff that unit tests
         | should pick up. It's easy to write a test case for
         | "CountingStack.pushAll() counts once per given element"
        
           | aflag wrote:
           | Tests are not bullet proof and subtle errors may go
           | uncatched. If you can completely eliminate a source of
           | errors, why not?
        
         | username90 wrote:
         | Why create virtual methods in a class if you have no intention
         | of it being a parent class? And if you intend for it to be a
         | parent class you can design it so that these things aren't an
         | issue.
        
           | valenterry wrote:
           | Could you give a code example of how you would design things
           | so that they aren't an issue?
        
             | username90 wrote:
             | The same way you design interfaces. If you can write good
             | interfaces then you can write good base classes, its all
             | about the contract between methods. Inheritance gets a bad
             | rap because most programmers used it with classes that were
             | never written to have interface contracts and therefore
             | they create unintended coupling. Intended coupling is a
             | good thing though, so as long as you have the same
             | discipline when creating base classes as you have when
             | creating interfaces then inheritance isn't an issue.
        
         | 37ef_ced3 wrote:
         | Excellent example. Let me point out that Go's approach to
         | inheritance ("embedding") doesn't suffer from the problem you
         | illustrated. A method on the embedded type can't call a method
         | on the type into which it is embedded. Go's approach to
         | inheritance is a big productivity win, and it's easy to
         | understand
        
         | ajuc wrote:
         | > This problem is impossible to fix
         | 
         | People write working code using inheritance, it's not
         | impossible, just requires extra care. One example:
         | class CountingStack() {            def push(element) returns
         | nothing =                count += 1; super.push(element)
         | override def pop() returns element =                count -= 1;
         | super.pop(element)         }
         | 
         | Another way is to use non-virtual methods in the base class
         | where you don't want people to modify your code. In C++ it's
         | even the default. So you should consciously think when you make
         | a method virtual.                   class Stack() {
         | def nonvirtual private push_internal(element) returns nothing =
         | ...            def push(element) returns nothing =
         | push_internal(element)            def pushAll(elementlist)
         | returns nothing = ... // use push_internal if needed
         | def pop() returns element = ...         }
         | 
         | Anyway - I agree inheritance is bad, and I dislike OOP in
         | general, just wanted to nitpick.
         | 
         | IMHO the main problem is encapsulation. People expect the code
         | to work one way and don't check their assumptions because of
         | the whole tower of abstractions and hiding what actually
         | happens behind layers. And then instead of writing
         | straightforward code to do what has to be done - you have to
         | tiptoe around all possible implementations :)
        
           | valenterry wrote:
           | Your first example will still break if the base class changes
           | behavior, so I think my point holds.
           | 
           | I agree with the non-virtual methods though - they are pretty
           | what I would call conceptual composition.
        
         | rectang wrote:
         | In your view, would this critique also apply to a trait-based
         | version in Rust which supplies a default implementation for
         | `push_all`?
         | 
         | https://play.rust-lang.org/?version=stable&mode=debug&editio...
         | trait Stack {             fn push(&mut self, element: i32);
         | fn push_all(&mut self, all: Vec<i32>) {                 for
         | element in all {                     self.push(element);
         | }             }             fn pop(&mut self) -> Option<i32>;
         | }
         | 
         | As far as I know it's impossible to invoke `super` to get at
         | the default version of `push_all` from an `impl` which
         | overrides `push_all`.
         | 
         | This is still "implementation inheritance", because the
         | implementing type inherits the default implementation of
         | "push_all". But it seems less brittle than classical OOP
         | implementation inheritance.
         | 
         | * No "super" invocations.
         | 
         | * Shallow inheritance hierarchies.
         | 
         | * No direct member variable access from the trait (interface)
         | code.
        
         | edflsafoiewq wrote:
         | This is only a problem is the methods on Stack are virtual. If
         | they are non-virtual, inheritance works like your composition
         | example: inheriting from Base basically creates a Base member
         | variable, except missing methods can be automatically forwarded
         | to the Base member. This saves you from having to explicitly
         | wrap all the methods of Stack in CountingStack.
         | 
         | Virtual should be used sparingly.
        
           | taejo wrote:
           | Agreed. Of the OOP languages I use, only C++ by default, and
           | I think this is one of the few defaults in C++ that is
           | correct. In Java terms, _every_ method should be `final`
           | except those that are explicitly intended as customization
           | points.
        
         | saiojd wrote:
         | Thank you, this is exactly it. The problem with inheritance is
         | specifically overloading, and not anything else. Composition as
         | it is being used currently has its own drawbacks (need to
         | manually delegate every method, no clear model for instance
         | construction, the need to duplicate fields across classes,
         | etc.). I wish overloading was just removed from inheritance or
         | that composition had better language support.
        
         | titzer wrote:
         | There 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.
        
           | valenterry wrote:
           | I think for the usecase you mention, there is a different
           | solution that I personally prefer.
           | 
           | Optimally, if your language supports it, just define an
           | interface with these 5 methods and then define extension
           | methods that work on any type that implements the interface.
           | The reason why this works is that all the other functions are
           | usually helper functions / convenience functions and they
           | only need the other 5 functions to work, so no need to access
           | any inner/private properties.
           | 
           | That is by far the most lightweight solution, and it works
           | even for 3rd-party libraries where you can't control the
           | types.
           | 
           | If your language does not have extension methods, then you
           | can still use composition just like in the example I gave and
           | delegate to the base class. That is a bit more code to write
           | (because you have to delegate to the non-5 methods if you
           | language doesn't automate that for you, some do) but I find
           | it cleaner than hoping that no one overwrites any non-5
           | method.
           | 
           | But yeah, essentially what you are saying here is to ask
           | people to follow the rule I gave by convention - depending on
           | that context that might work as well.
        
             | titzer wrote:
             | Yeah, I generally like composition better, but sometimes
             | the coupling between the base class and those subclasses
             | that provide the 5 methods is just too strong to ignore,
             | and if you break out the methods into another interface,
             | then you are struggling to find a place to put the shared
             | logic (which you proposed to do with extension methods).
             | 
             | Your example with the counting stack made use of "super",
             | which is always a red flag to me. "super" is such a bad
             | smell to me, that I literally never use it. In fact, in
             | Virgil, my language project
             | (http://github.com/titzer/virgil), there is no super
             | construct at all, nor static methods or interfaces for that
             | matter. 15 years of writing it, 200k lines later, and I can
             | say that personally, having delegates, first-class
             | functions, partial application, and tuples go way further
             | than more complex trait/interface/extension method madness.
        
               | Serow225 wrote:
               | Re the shared logic, Java and C# have the concept of
               | "interface default methods" which is what I'd use for
               | your case, if I understand it right.
        
               | valenterry wrote:
               | Not sure if I understand you correctly here. With "the
               | coupling ... is just too strong to ignore" you mean that
               | if someone has one of the subclasses at hand, they should
               | automatically have all the extension methods at hand not
               | having to look for them somewhere?
               | 
               | > Your example with the counting stack made use of
               | "super", which is always a red flag to me. "super" is
               | such a bad smell to me, that I literally never use it.
               | 
               | I think that's a sign that you have developed a good
               | intuition of that it can lead to problems! :)
        
           | ptx wrote:
           | This pattern is also called the _template method pattern_ , I
           | believe:
           | 
           | https://refactoring.guru/design-patterns/template-method
        
             | titzer wrote:
             | Yep, that's it. Thanks for the reference!
        
       | tarkin2 wrote:
       | Inheritance 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
        
         | trgn wrote:
         | This work-around copies the values (in your examples), or the
         | references (if the props are functions or objects), which is
         | just wasted cycles and memory bloat. It's harder for runtimes
         | to optimize, because with that mixin `o1` changes its shape.
         | It's harders for IDEs to infer the type of `o1`, which will
         | hurt navigating and searching through your codebase with
         | confidence.
         | 
         | Implementing OOP in JS with hacks like this is worse, in pretty
         | much all ways, apart from the intellectual kick many devs get
         | of avoiding OOP at all cost, as opposed to just using built-in
         | language features.
        
           | tarkin2 wrote:
           | I agree copying values is not massively smart. (Used for
           | simplicity of example)
           | 
           | But copying function pointers seems negligible. I can't see
           | this being avoided even in traditional oops.
        
         | tsimionescu wrote:
         | While 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 wrote:
           | Override support, no?                 function f() {
           | function g() {           return 1         }         return {
           | g }       }            function f1(f) {         function g()
           | {           return f.g() + 1         }         return { g }
           | }            f1(f())
        
             | tsimionescu wrote:
             | Sure, 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).
        
       | qalmakka wrote:
       | In 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.
        
         | d1plo1d wrote:
         | Very true. I'm _checks cloc_ 14,000 lines into a personal
         | project and I have yet to feel any need to use `dyn` (virtual
         | dispatching for non-Rust folks).
        
           | pierrebai wrote:
           | "I've designed this project in Go following Go naturally
           | imposed design patterns and found that I did not need
           | inheritance.", said the Go programmer.
           | 
           | "Well, mmm, duh?", thought programmers of other languages.
           | 
           | "I've designed this project in Rust, following Rust-imposed
           | design principles, and found monomorphism sufficient", said
           | the Rust programmer.
           | 
           | "Well, mmm, duh?", thought programmers of other languages.
           | 
           | "I've designed this project in Python, using Python-style
           | design and I've found that I did not need strictly typed
           | functions", said the Python programmer.
           | 
           | "Well, mmm, duh?", thought programmers of other languages.
           | 
           | "I've designed this project in language X, which impose Y and
           | as such I've found that you actually don't need Z", said the
           | X programmer.
           | 
           | "Well, we got the picture already", said the HN reader.
        
             | politician wrote:
             | Absolutely, but the interesting bits are in the metrics.
             | How many lines of code does it take in each language? How
             | many distinct concepts are required for the solution? To
             | what degree is the mastery of each concept required?
        
         | cout wrote:
         | That'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.
        
         | zozbot234 wrote:
         | Traits _are_ virtual dispatching, albeit the dispatch table is
         | not bundled with the object. The meaningful distinction is
         | between implementation inheritance vs. inheritance of
         | interfaces, abstract classes or traits.
        
           | Twisol wrote:
           | Trait objects (via `dyn`) are virtual dispatching, but traits
           | themselves are basically agnostic to static vs. virtual
           | dispatch. If you use a trait method through a statically
           | known type, the method is invoked statically -- no runtime
           | lookup.
        
         | SMFloris wrote:
         | Recently 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&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/#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.
        
           | ptato wrote:
           | Would that copy-paste code ever amount to more than some
           | getter methods, like the get_speed() example you provided? If
           | the speed field had some special behaviour, couldn't you wrap
           | it in its own Speed type, with its own methods, and use that
           | from the enclosing types?
        
             | adrianmsmith wrote:
             | I think the solution you've proposed is probably how you'd
             | do it - but isn't inheritance more elegant in this case
             | (i.e. using a language which supports inheritance if this
             | is important to you)
        
               | ptato wrote:
               | Maybe not on every situation, but I do agree that
               | inheritance is more elegant in a lot of situations, in
               | the sense that it gets some behaviour "out of the way".
        
               | sanderjd wrote:
               | No, inheritance is not an elegant way to mix in shared
               | data and behavior. It can seem like it is in a simple
               | case where there is only one set of data and behavior you
               | want to mix in, but it scales very poorly. Nothing is
               | fundamentally a single kind of thing, and multiple
               | inheritance is a mess. It is more elegant to mix the
               | behavior in through delegation, because it scales to
               | however many things you want to mix in. Carrying on the
               | synthetic example in this thread, you can mix in Speed
               | and Pedals into Bicycle but Speed and Cylinders into Car.
        
           | estebank wrote:
           | Beyond some small changes I would make to use new-types all
           | over the place, I personally like to rely on Deref impls to
           | mimic inheritance (although not everyone agrees this is a
           | good idea to do too often): https://play.rust-
           | lang.org/?version=stable&mode=debug&editio...
           | 
           | As you can see I also used a macro by example there to remove
           | some of the duplicated code that you would otherwise have.
        
           | leshow wrote:
           | Monomorphization is not a user action of copy pasting, it's
           | something the compiler does with parametric code. If we're
           | talking about Rust it means when you write:
           | fn id<T>(t: T) { }              fn main() {
           | id(String::from("foo"));            id(1_usize);         }
           | 
           | The Rust compiler will generate a method of `id` that works
           | for both String and usize, so there will be two copies of the
           | function with a slight variation in your binary. That process
           | is called monomorphization.
           | 
           | > 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.
           | 
           | You can easily use generics, there is no reason to copy paste
           | code like this
        
           | twic wrote:
           | I'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
           | 
           | Inheritance is one possible solution. There are others.
        
             | SMFloris wrote:
             | I do hope they adopt one solution or the other. Rust for me
             | seems incomplete because of this.
             | 
             | Another issue I have with it is perfectly described in this
             | article: https://theta.eu.org/2021/03/08/async-rust-2.html
        
               | d1plo1d wrote:
               | For reference I have over a decade of JavaScript
               | experience in industry and my async Rust rewrite of a
               | large JS project was *more* concise then the heavily
               | refactored and polished NodeJS version (a language I
               | consider more concise then most). If you are having to
               | copy and paste excessively in Rust that is an issue but
               | it is not necessarily intrinsic to the language.
               | 
               | For what it's worth traits largely prevented copy and
               | paste and where traits fail there are macros. The classic
               | inheritance example you link to is a tiny percentage of
               | my code and an orders of magnitude smaller time sink when
               | compared to the code maintenance problems I faced in
               | other languages.
        
           | [deleted]
        
           | devit wrote:
           | You can use a macro instead of copy and pasting.
           | 
           | You can also do this:                 struct Vehicle       {
           | speed: f32,         type: VehicleType       }            enum
           | VehicleType       {         Car(...),         Bicycle(...)
           | }
           | 
           | Or this (although this is the least common):
           | struct Vehicle       {         data: VehicleData
           | type: Box<dyn SpecificVehicle>       }            struct
           | VehicleData       {         speed: f32,       }
           | trait SpecificVehicle       {         fn quack(&self, data:
           | &VehicleData);       }            impl SpecificVehicle for
           | Car {...}       impl SpecificVehicle for Bicycle {...}
        
             | [deleted]
        
           | dwattttt wrote:
           | You actually can have a single queue that handles the
           | different cases. You want dynamic dispatch for that, the
           | syntax looks like this (quick addition to the playground
           | sample): https://play.rust-
           | lang.org/?version=stable&mode=debug&editio...
        
             | rst wrote:
             | ... which does put both cars and bicycles in the same
             | queue, but doesn't eliminate the copy-paste for each new
             | type completely; 'car' and 'bicycle' still wind up with
             | separate 'get_speed' impls, which are textually identical
             | aside from the type names.
        
               | cyphar wrote:
               | Usually the way people solve this in Rust is with macros.
        
               | Tyr42 wrote:
               | Sure, traits talk about functions, not properties/ strict
               | fields, so you need to provide trivial getters if you
               | want to abstract over properties.
               | 
               | True, but not that interesting? Sure we could have some
               | Ruby :get_attrs magic or whatever.
        
       | slver wrote:
       | Inheritance 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.
        
         | rectang wrote:
         | > _Honestly I 've not seen such a strong characterization of
         | inheritance as being purely semantic._
         | 
         | As articulated elsewhere in the discussion, classical
         | inheritance has a great affinity for the "specialization"
         | design pattern, which is everywhere. Classical inheritance is
         | not just a performance hack, it is _semantically compelling_ ,
         | as illustrated by the enduring popularity of "Cat Extends
         | Animal"!
         | 
         | Furthermore, single inheritance has a really elegant canonical
         | implementation: "extending" structs and vtables in subclasses
         | by appending their member variables and new virtual method
         | pointers.
         | 
         | Using composition may inoculate against certain brittleness
         | problems in code sharing, but if "has-a" is cumbersome to code
         | up, no amount of scolding is going to change mass behavior.
         | 
         | Emphasizing interface inheritance as an alternative to
         | classical inheritance seems to be more effective at breaking
         | people away from the problems of implementation inheritance,
         | even though interface inheritance is still "is-a" and not
         | "has-a". And "fat pointers" with two words, one for the
         | dispatch table and one for the object, are an elegant canonical
         | implementation for interface inheritance.
         | 
         | > _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._
         | 
         | This sort of "everyone knows" dissing of inheritance is why I
         | tend to dislike HN discussions of it.
         | 
         | For what it's worth, the rust-by-example for traits uses the
         | Animal "schtick" unironically:
         | 
         | https://doc.rust-lang.org/rust-by-example/trait.html
        
           | Twisol wrote:
           | > Classical inheritance is not just a performance hack, it is
           | semantically compelling
           | 
           | I think often it's compelling for misleading reasons. For
           | example, is a square a rectangle? Mathematically, yes. But in
           | mathematics, we don't mutate values (we would describe an
           | entity's evolution as a series of values).
           | 
           | If you are allowed to mutate the dimensions of a rectangle
           | object, then for a square to be a rectangle, it must set both
           | dimensions when setting either, or otherwise cause an error
           | if its dimensions get out of sync. If you can, say, get the
           | area of a rectangle, Liskov's principle of behavioral
           | subtyping suggests that such a square would break the
           | expectations of a client of rectangles ("I changed the width
           | but now I'm getting the wrong area!"), so a square is not
           | really a rectangle. You may recover behavioral subtyping if
           | you explicitly limit the kinds of reasonable inferences a
           | client can make from a rectangle, but that may limit your use
           | cases for actual rectangles.
           | 
           | I like this phrasing from one of the answers to this SO
           | question [0]:
           | 
           | > The problem is that what is being described is really not a
           | "type" but an cumulative emergent property.
           | 
           | > All you really have is a quadrilateral and that both
           | "squareness" and "rectangleness" are just emergent artifacts
           | derived from properties of the angles and sides.
           | 
           | Put differently, it's very tempting to treat "square" as a
           | specialization of "rectangle", but that has very little to do
           | with their _intrinsic_ definitions and far more to do with
           | what can be _observed_ of them by the program in context.
           | 
           | [0] https://stackoverflow.com/a/1030559/159876
        
         | CuriouslyC wrote:
         | I 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 wrote:
           | > 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.
        
             | CuriouslyC wrote:
             | I think the practice of not using a tool because people
             | could potentially misuse it is misguided in this day and
             | age. Linting and static code analysis are quite capable of
             | enforcing good usage patterns.
        
               | slver wrote:
               | The problem is "developer" spans a gigantic range of
               | capabilities. Someone who wrote Hello World in Chrome's
               | Console is a developer, and someone writing kernel code
               | professionally is also a developer.
               | 
               | It's hard to define a "good practice" for this wildly
               | heterogenous group. What's good for a beginner (training
               | wheels on a child's bike), is completely counter-
               | productive to a pro (motorcycle sports driver).
        
               | mumblemumble wrote:
               | My concern is not exactly that it _could_ potentially be
               | misused. It 's more that it seems to set up forces that
               | subtly push projects toward becoming resistant to change
               | over time. And, while it's possible for individual
               | programmers to exercise discipline in order to push back
               | against these sorts of forces, at a larger scale the
               | guiding principle seems to be, "water flows downhill." So
               | we should seek to find ways of building things that
               | generally set up forces guiding us toward good design in
               | the long run, _without_ having to drill people on large
               | laundry lists of best practices. That approach only works
               | for as long as the code is owned by a sort of benevolent
               | dictator who is able to, by hook or by crook, keep all
               | their teammates on (their version of) the righteous path.
               | And that approach itself is unstable; it has a tendency
               | to degrade rapidly whenever the leader decides to spend a
               | week at the beach. If they should ever leave the company,
               | it 's likely to be lost forever.
               | 
               | I can't say that I know a software development idiom that
               | reliably creates a more stable equilibrium point. But I
               | don't think we'll ever find one unless we're willing to
               | examine the failure modes of existing paradigms.
        
           | slver wrote:
           | I think thanks to Java opting to use "implements" for
           | interfaces, people no longer associate "inheritance" as the
           | thing we do when we write a fully abstract class (i.e.
           | interface) and then "inherit" this abstraction to implement
           | it. Interfaces are, of course, crucial.
           | 
           | Not sure I understood your example about the static class vs.
           | function factory tbh though.
        
             | TheCoelacanth wrote:
             | That's not inheritance. That's polymorphism.
             | 
             | Java doesn't let you have inheritance without polymorphism,
             | but it is possible, see "private inheritance" in C++.
        
             | rectang wrote:
             | But I don't think a "fully abstract class" is the same
             | thing as an "interface", at least in Java? As far as I
             | know, you can implement multiple "interfaces" but you can
             | still only "extend" one abstract class, even if it is
             | "fully abstract" in that it has no concrete member
             | variables and all methods are abstract.
        
               | slver wrote:
               | In Java it isn't but this is specific to Java (and clones
               | of Java like C#).
               | 
               | This is because Java has single-inheritance enforcement
               | for classes.
               | 
               | C++ for example has multiple inheritance. So the way you
               | do an interface is you just write an abstract class, then
               | extend it to implement it.
        
               | johnmaguire wrote:
               | In my experience, whether you use an abstract class or an
               | interface class as your reference depends very much on
               | the problem you are trying to solve - specifically, is
               | there common code shared across all implementations?
        
               | mtinkerhess wrote:
               | A C++ pure abstract class is equivalent to a Java
               | interface.
        
             | CuriouslyC wrote:
             | As a quick example...
             | 
             | class ServiceWrapper:                 service = ...
             | v1 = ...            v2 = ...            @static
             | def my_job():              return service.call(v1, v2)
             | 
             | vs.
             | 
             | def create_service_wrapper(v1=..., v2=..., service=...):
             | def f():              return service.call(v1, v2);
             | return f
             | 
             | The class can scale to multiple methods sharing parameters,
             | but the semantics of the factory fall apart if you want to
             | return more than one parameterized function.
        
         | [deleted]
        
       | whoisburbansky wrote:
       | I 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.
        
         | esyir wrote:
         | People 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?
        
           | sjfidsfkds wrote:
           | You just invented society!
        
           | IncRnd wrote:
           | "Working hard and creating value" and "biological desires"
           | are multiple inheritance, which is outside the scope of this
           | article.
        
             | vbsteven wrote:
             | Is this the diamond problem?
        
         | hkmurakami wrote:
         | "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.
        
         | [deleted]
        
         | MeinBlutIstBlau wrote:
         | I figured cause it was on HN that there was no way that was the
         | article
        
       | dboreham wrote:
       | Seems 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.
        
       ___________________________________________________________________
       (page generated 2021-04-30 23:03 UTC)