Live data from Hacker News

Inheritance was invented as a performance hack (2021)

catern.com

91–100 of 252 posts

Re: Inheritance was invented as a performance hack (2021)

#91
post #36

Earlier quoted context omitted.

> The bottom line is, no one ever really used inheritance that much anyway If you think that, you have no idea how much horrible code is out there. Especially in enterprise land, where deadlines are set by people who get paid by the hour. I once worked on a java project which had a method - call a method - call a method - call a method and so on. Usually, the calls were via some abstract interface with a single imple…

"But there's a reason the crowd is moving against inheritance" Yes, in our fad-chasing industry the pendulum has moved in the other direction. Let's wait few years. There is nothing wrong with OOP, inheritance, FP, procedural, declarative or whatever. What is bad is religious dogma overtaking engineering work.

I’m surprised this is considered a controversial take.

You can write spaghetti in any language or paradigm. People will go overboard on DRY while ignoring that inheritance is more or less just a mechanism for achieving DRY for methods and fields.

FP wizards can easily turn your codebase into a complex organism that is just as “impenetrable” as OOP. But as you say, fads are fads are fads, and OOP was the previous fad so it behooves anyone who wants to look “up to date” to be performative about how they know better.

Personally I think it’s obvious that anyone passing around structs that contain data and functions that act on that data is the same concept as passing around objects. I expect you can even base a trait off of another trait in Rust.

But don’t dare call it what it actually is, because this industry really is as petulant as you describe.

Re: Inheritance was invented as a performance hack (2021)

#92
post #36

Earlier quoted context omitted.

Yeah ya... everyone likes to go on and on about how inheritance is the root of all evil and if you just don't use it, everything will be fine. Sorry, it won't be fine. Your software will still be a mess unless it is small and written three times by the same person who knows what they are doing. The bottom line is, no one ever really used inheritance that much anyway (other than smart people trying to outsmart themsel…

> The bottom line is, no one ever really used inheritance that much anyway If you think that, you have no idea how much horrible code is out there. Especially in enterprise land, where deadlines are set by people who get paid by the hour. I once worked on a java project which had a method - call a method - call a method - call a method and so on. Usually, the calls were via some abstract interface with a single imple…

[deleted]

Re: Inheritance was invented as a performance hack (2021)

#93
post #51

Earlier quoted context omitted.

> I don't think Inheritance is always bad - sometimes it's a useful tool. I can only think of one or two instances where I've really been convinced that inheritance is the right tool. The only one that springs to mind is a View hierarchy in UI libraries. But even then, I notice React (& friends) have all moved away from this approach. Modern web development usually makes components be functions. (And yes, javascript…

The problem is of course that there is no useful default behavior you can define when the trait is so isolated and generic.

It doesn't have to be "so isolated". The trait can still have required methods that don't have a default implementation. Eg:

    trait Robot {
        fn send_command(&mut self, command: Command);

        fn stop(&mut self) {
            self.send_command(Command.STOP);
        }
    }

    struct BenderRobot;
    
    impl Robot for BenderRobot {
        // Required.
        fn send_command(&mut self, command: Command) { todo!(); } 
    }
This is starting to look a lot like C++ class inheritance. Especially because traits can also inherit from one another. However, there are two important differences: First, traits don't define any fields. And second, BenderRobot is free to implement lots of other traits if it wants, too.

If you want a real world example of this, take a look at std::io::Write[1]. The write trait requires implementors to define 2 methods (write(data) and flush()). It then has default implementations of a bunch more methods, using write and flush. For example, write_all(). Implementers can use the default implementations, or override them as needed.

Docs: https://doc.rust-lang.org/std/io/trait.Write.html

Source: https://doc.rust-lang.org/src/std/io/mod.rs.html#1596-1935

Re: Inheritance was invented as a performance hack (2021)

#94
post #43
post #36

Earlier quoted context omitted.

> The bottom line is, no one ever really used inheritance that much anyway If you think that, you have no idea how much horrible code is out there. Especially in enterprise land, where deadlines are set by people who get paid by the hour. I once worked on a java project which had a method - call a method - call a method - call a method and so on. Usually, the calls were via some abstract interface with a single imple…

I don't think Inheritance is always bad - sometimes it's a useful tool. But it was definitely overused and composition, interfaces work much better for most problems. Inheritance really shines when you want to encapsulate behaviour behind a common interface and also provide a standard implementation. I.e: I once wrote a RN app which talked to ~10 vacuum robots. All of these robots behaved mostly the same, but each wa…

Inheritance is not the only way to share behavior across different implementations — it'a just the only way available in the traditional 1990s crop of static OOP languages like C++, Java and C#.

There are many other ways to share an implementation of a common feature:

1. Another comment already mentioned default method implementations in an interface (or a trait, since the example was in Rust). This technique is even available in Java (since Java 8), so it's as mainstream as it gets.

The main disadvantage is that you can have just one default implementation for the stop() method. With inheritance you could use hierarchies to create multiple shared implementations and choose which one your object should adopt by inheriting from it. You also cannot associate any member fields with the implementation. On the bright side, this technique still avoids all the issues with hierarchies and single and multiple inheritance.

2. Another technique is implementation delegation. This is basically just like using composition and manually forwarding all methods to the embedded implementer object, but the language has syntax sugar that does that for you. Kotlin is probably the most well-known language that supports this feature[1]. Object Pascal (at least in Delphi and Free Pascal) supports this feature as well[2].

This method is slightly more verbose than inheritance (you need to define a member and initialize it). But unlike inheritance, it doesn't requires forwarding the class's constructors, so in many cases you might even end up with less boilerplate than using inheritance (e.g. if you have multiple overloaded constructors you need to forward).

The only real disadvantage of this method is that you need to be careful with hierarchies. For instance, if you have a Storage interface (with the load() and store() methods) you can create EncryptedStorage interface that wraps another Storage implementation and delegates to it, but not before encrypting everything it sends to the storage (and decrypting the content on load() calls). You can also create a LimitedStorage wrapper than enforces size quotas, and then combine both LimitedStorage and EncryptedStorage. Unlike traditional class hierarchies (where you'd have to implement LimitedStorage, EncryptedStorage and LimitedEncryptedStorage), you've got a lot more flexibility: you don't have to reimplement every combination of storage and you can combine storages dynamically and freely. But let's assume you want to create ParanoidStorage, which stores two copies of every object, just to be safe. The easiest way to do that is to make ParanoidStorage.store() calls wrapped.store() twice. The thing you have to keep in mind, is that this doesn't work like inheritance: For instance, if you wrap your objects in the order EncryptedStorage(ParanoidStorage(LimitedStorage(mainStorage))), ParanoidStorage will call LimitedStorage.store(). This is unlike the inheritance chain EncryptedStorage 3. Dynamic languages almost always have at least one mechanism that you can use to automatically implement delegation. For instance, Python developers can use metaclasses or __getattr__[3] while Ruby developers can use method_missing or Forwaradable[4].

4. Some languages (most famously Ruby[5]) have the concept of mixins, which let you include code from other classes (or modules in Ruby) inside your classes without inheritance. Mixins are also supported in D (mixin templates). PHP has traits.

5. Rust supports (and actively promotes) implementing traits using procedural macros, especially derive macros[6]. This is by far the most complex but also the most powerful approach. You can use it to create a simple solution for generic delegation[7], but you can go far beyond that. Using derive macros to automatically implement traits like Debug, Eq, Ord is something you can find in every codebase, and some of the most popular crates like serde, clap and thiserror rely on heavily on derive.

[1] https://kotlinlang.org/docs/delegation.html

[2] https://www.freepascal.org/docs-html/ref/refse48.html

[3] https://erikscode.space/index.php/2020/08/01/delegate-and-de...

[4] https://blog.appsignal.com/2023/07/19/how-to-delegate-method...

[5] https://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.h...

[6] https://doc.rust-lang.org/reference/procedural-macros.html#d...

[7] https://crates.io/crates/ambassador

Re: Inheritance was invented as a performance hack (2021)

#95
post #83

Earlier quoted context omitted.

Java wasn't the first to do that Objective-C (10? years before) had interfaces. Even C++ has that with multiple inheritance - some parents can just be interfaces. As to whether Smalltalk needs interfaces see https://stackoverflow.com/a/7979852/151019 and https://www.jot.fm/issues/issue_2002_05/article1/

Objective-C and Smalltalk were always niche languages, at least by comparison to Java and C#, and I think Smalltalk fans underestimate the value of many things. C++ does not (or at least did not at the time) have a concept of interfaces. There was a pattern in some development communities for defining interfaces by writing classes that followed particular rules, but no first-class support for them in the language.

>no first-class support for them in the language.

An interface is just a base class none of whose virtual functions have implementations. C++ has first class support for it. The only thing C++ lacks is the "interface" keyword.

Re: Inheritance was invented as a performance hack (2021)

#96
post #51
post #43

Earlier quoted context omitted.

I don't think Inheritance is always bad - sometimes it's a useful tool. But it was definitely overused and composition, interfaces work much better for most problems. Inheritance really shines when you want to encapsulate behaviour behind a common interface and also provide a standard implementation. I.e: I once wrote a RN app which talked to ~10 vacuum robots. All of these robots behaved mostly the same, but each wa…

> I don't think Inheritance is always bad - sometimes it's a useful tool. I can only think of one or two instances where I've really been convinced that inheritance is the right tool. The only one that springs to mind is a View hierarchy in UI libraries. But even then, I notice React (& friends) have all moved away from this approach. Modern web development usually makes components be functions. (And yes, javascript…

> The only one that springs to mind is a View hierarchy in UI libraries.

I'd like to generalize that a little bit and say: graph structures in general. A view hierarchy is essentially a tree, where each node has a bunch of common bits (tree logic) and a bunch of custom bits (the actual view). There are tons of "graph structures" that fit that general pattern: for instance, if you have some sort of data pipeline DAG where data comes in on the left, goes out on the right, and in the middle has to pass through a bunch of transformations that are linked in some kind of DAG. Inheritance is great for this: you just have your nodes inherit from some kind of abstract "Node" class that handles the connection and data flow, and you can implement your complex custom behaviors however you want and makes it very easy to make new ones.

I'm very much in agreement that OOP inheritance has been horrendously overused in the 90s and 00s (especially in enterprise), but for some stuff, the model works really well. And works much better than e.g. sum types or composition or whatever for these kinds of things. Use the right tool for the right job, that's the central point. Nothing is one-size-fits-all.

Re: Inheritance was invented as a performance hack (2021)

#97
post #10

I'm not sold the evidence is there to show inheritance is a good idea - it basically says that constructors, data storage and interfaces need to be intertwined. That isn't a very powerful abstraction, because they don't need to be and there isn't an obvious advantage from doing so over picking up the concepts separately as required. And inheritance naturally suggests grouping interfaces into a tree in the way that se…

> a tree probably doesn't represent the fundamental truth of things It does. Trees appear in nature all the time. It's the basis of human society, evolution and many things. Most of programming moves towards practicality rather then fundamental truth. That's why you get languages like golang which are ugly but practical.

A city is not a tree: https://www.patternlanguage.com/archive/cityisnotatree.html

Even trees are not trees: https://en.wikipedia.org/wiki/Anastomosis

Evolution is most definitely not a tree.

Nature also tends towards practicality, even more so than programming. Trees aren’t a fundamental truth, they’re a made-up oversimplified abstraction.

Re: Inheritance was invented as a performance hack (2021)

#98
post #35

Earlier quoted context omitted.

How are interfaces with ability to provide default implementations for members (which both C# and Java allow today) not a substitute for mixins? "Only reference types can implement interfaces" is simply not true in C#. Not only can structs implement them, but they can also be used through the interface without boxing (via generics).

> How are interfaces with ability to provide default implementations for members (which both C# and Java allow today) not a substitute for mixins? Those default-implementations are only accessible when the object is accessed via that interface; i.e. they aren't accessible as members on the object itself. Furthermore, interfaces (still) only declare (and optionally define) vtable members (i.e. only methods, properties…

> Those default-implementations are only accessible when the object is accessed via that interface; i.e. they aren't accessible as members on the object itself.

That's true in C# but not in Java, so it's not something intrinsic to the notion of an interface.

> Furthermore, interfaces (still) only declare (and optionally define) vtable members (i.e. only methods, properties, and events - which are all fundamentally just methods), not fields or any kind of non-static state

This is true, but IMO largely irrelevant because get/set accessors are a "good enough" substitute for a field. That there is even a distinction between fields and properties in the first place is a language-specific thing; it doesn't exist in e.g. Eiffel.

Re: Inheritance was invented as a performance hack (2021)

#99
post #30

This title is so wild when you read it without the context of software development...

True, before opening it I thought it was about actual transfer of wealth from parents to children. Which also seems likt a big performance hack.

The "invented" part was suspicious though.

Re: Inheritance was invented as a performance hack (2021)

#100
post #51
post #43

Earlier quoted context omitted.

I don't think Inheritance is always bad - sometimes it's a useful tool. But it was definitely overused and composition, interfaces work much better for most problems. Inheritance really shines when you want to encapsulate behaviour behind a common interface and also provide a standard implementation. I.e: I once wrote a RN app which talked to ~10 vacuum robots. All of these robots behaved mostly the same, but each wa…

> I don't think Inheritance is always bad - sometimes it's a useful tool. I can only think of one or two instances where I've really been convinced that inheritance is the right tool. The only one that springs to mind is a View hierarchy in UI libraries. But even then, I notice React (& friends) have all moved away from this approach. Modern web development usually makes components be functions. (And yes, javascript…

> But even then, I notice React (& friends) have all moved away from this approach. Modern web development usually makes components be functions.

But what do those functions return? Oh look, it's DOM nodes, which are described by and implemented with inheritance.

I would agree that view hierarchies in UI libraries are one of the primary use-cases for inheritance. But it's a pretty big one.

Post reply on HN