Live data from Hacker News

Inheritance was invented as a performance hack

catern.com

211–220 of 268 posts

Re: Inheritance was invented as a performance hack

#211

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/

If it has a Snopes article debunking it, it's the inverse of the lucky 10,000 since "everyone knows" the wrong fact

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.

Re: Inheritance was invented as a performance hack

#212
post #197

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

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 i…

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.

Re: Inheritance was invented as a performance hack

#213

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

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 obje…

Override support, no?

  function f() {
    function g() {
      return 1
    }
    return { g }
  }

  function f1(f) {
    function g() {
      return f.g() + 1
    }
    return { g } 
  }

  f1(f())

Re: Inheritance was invented as a performance hack

#214

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.

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.

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.

Re: Inheritance was invented as a performance hack

#215
post #84
post #49

Earlier quoted context omitted.

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 thos…

This pattern is also called the template method pattern , I believe: https://refactoring.guru/design-patterns/template-method

Yep, that's it. Thanks for the reference!

Re: Inheritance was invented as a performance hack

#216

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.

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 no…

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
  }

  struct VehicleData
  {
    speed: f32,
  }

  trait SpecificVehicle
  {
    fn quack(&self, data: &VehicleData);
  }

  impl SpecificVehicle for Car {...}
  impl SpecificVehicle for Bicycle {...}

Re: Inheritance was invented as a performance hack

#217
post #199

Earlier quoted context omitted.

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.

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.

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++.

Re: Inheritance was invented as a performance hack

#219
post #171

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 fe…

> 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 i…

> 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

Re: Inheritance was invented as a performance hack

#220
post #121

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 t…

> 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

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 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.

Post reply on HN