6 ms·
Inheritance is static composition. Everything we do statically is for two reasons: 1. Static invariants (not subject to runtime-defined conditions). 2. Perfor
by slver 5y ago
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.
- CuriouslyC 5y agoI frequently hear people malign inheritance, and while it can obfuscate code in some circumstances, it can also produce code that is easily and clearly extendable. For example, a class with a static method that uses class properties to control behavior is cleaner than a function factory that takes a config object. Interface inheritance is also quite useful.
- mumblemumble 5y ago> it can also produce code that is easily and clearly extendable It can also produce code that is not easily and clearly extendable, if the behavior you're trying to extend is buried a layer or two further down the inheritance hierarchy. Unfortunately, due to the First Law of Kipple, the rate at which you run into this problem is proportional to the age of the code base. And so we grow frustrated with implementation inheritance. Other extensibility mechanisms probably have similar pitfalls, but they haven't been the dominant way of doing things for long enough to accumulate the same volume of clutter. Don Knuth has mentioned in a few interviews that he isn't so hot on code reuse, and prefers code that's designed to be easy to edit over code that's designed to be easy to extend. I'm starting to see some wisdom in that idea. With the one, being able to keep things tidy is a primary goal. With the other, eventually tidying becomes a frightening enterprise, because you have to avoid upsetting the inheritance hierarchies that are precariously balanced on top of the code you're trying to tidy up.
- CuriouslyC 5y agoI 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 5y agoThe 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 5y agoMy 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 5y agoI 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.
- rectang 5y agoBut 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 5y agoIn 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 5y agoIn 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 5y agoA C++ pure abstract class is equivalent to a Java interface.
- TheCoelacanth 5y agoThat'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 5y ago> 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 https://doc.rust-lang.org/rust-by-example/trait.html
- Twisol 5y ago> 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 https://stackoverflow.com/a/1030559/159876
- deleted 5y ago[deleted]
- rurban 5y agoI'm sure you mixed that up. Inheritance is the dynamic variant of composition. You can dynamically extend objects by adding fields and methods to it's class, but not to composed objects, because they are statically closed. (Well in the good languages. javascript and friends still allow to extend objects, to violate their types). You can also dynamically mess with the inheritance search path, or fall into the diamond trap. Not with static (or maybe call it lexical) composition. Composition wastes a lot of space and disallows dynamic extensions, like plugin overrides, test mocking, ... But is very good with proper structural typing, and is a good performance hack, because it doesn't need to chase pointers at runtime, and neither do the inheritance search and linearization dance.