39 ms·
In all my experience with OOP, it's always been inheritance that is the root of all evil. Rust and Go got this correct by having class-like objects with no inhe
by ferdowsi 5y ago
In all my experience with OOP, it's always been inheritance that is the root of all evil. Rust and Go got this correct by having class-like objects with no inheritance, to achieve encapsulation without fragility.
Unfortunately, all the other languages that included inheritance in their design can't wish it away. Devs are going to keep reaching for inheritance as the closest, most comfortable abstraction.
- Zababa 5y ago> In all my experience with OOP, it's always been inheritance that is the root of all evil. I have this "theory" in the back of my head that trees are usually the wrong things to model thing in life but it's what come to us naturally. For example, a blog with categories and sub-catogories for articles (a tree, inheritence) can often describe the content better by using tags (a graph, composition). I think that's because trees are easy to deal with and understand, but graphs are more "open" with what you can do.
- necheffa 5y agoExcept that trees are by definition graphs with specific conditions on direction and cycles.
- db48x 5y agoPerhaps it is those very conditions which make trees less useful than they at first appear.
- duped 5y agoA tree is a graph where all vertices have exactly one path through them. Trees are often implemented using composition, and inheritance can be graphical (the Diamond Problem is not game over).
- jstanley 5y agoA / \ B C / \ / \ D E F G This is clearly a tree, but doesn't B have 2 paths through it? A->B->D and A->B->E.
- gmfawcett 5y agoYou're right. The correct definition is that any two vertices are connected by (i.e. are the endpoints of) exactly one path. Fun fact -- since graph-theory trees are undirected by definition, an inheritance graph is more properly called an arborescence (for single inheritance). For multiple inheritance it's a DAG (with diamond-pattern) or a directed tree (without).
- jholman 5y ago> any two vertices are connected by (i.e. are the endpoints of) exactly one path No. You just described a connected acyclic graph, not a tree. In addition to being connected and acyclic, a tree must also have a root, and is thus implicitly directed.
- hcarvalhoalves 5y agoData modelling in OOP is an exercise in coming up with Platonic ideals, resulting in a hierarchical (tree-like) ontology as you try to choose of the atributes as the categorisation dimension, and leaving everything else as properties. class Animal class Mammal inherits Animal class Feline inherits Mammal class Cat inherits Feline ... This is different than just asserting facts with data, which can lie in multiple dimensions. Is Feline Is Mammal Is Fluffy Is White Does Meow The later is a much more flexible data model as it more closely mimics observed (subjective) reality, and is less disturbed when a new (counter-)example is introduced, but is also harder to reason about than idealised categories.
- wwweston 5y agoHence traits?
- beaconstudios 5y agoAnd ECS is fundamentally a trait system!
- dkersten 5y agoI know all the terms are overloaded nowadays so everything’s kinda unclear but I always wished that ECS components had been called traits, because adding a component to an entity gives it some a trait like “this thing has a position in the world” or “this thing can be drawn” (and perhaps have systems named “behaviors”, because systems add behavior to entities based on the traits/components that they have) Years ago, when ECS was just starting to be talked about (after Adam Matrins blog posts), I wrote a toy ECS where I used that naming convention. Nowadays I stick to the mainstream terminology since that’s what other people know.
- beaconstudios 5y agoYeah I've commented before that "entity component system" is roughly synonymous with "thing piece thing" or something like that - it's a really bad name because it's so ambiguous. Anything including the term trait would be 1000x better because at least "trait" means something.
- dexwiz 5y agoHumans have a natural tendency to refine a single idea by splitting it into two based on a differentiating factor. This ends up looking like a tree when applied repeatedly. Dichotomous keys for species identification are another example of this.
- eska 5y agoThe problem being that they don't categorize things into one tree, but many. One can view the same thing in different ways. OOP tree hierarchies do not allow that. It's like trying to categorize your photos in a directory tree. Do you categorize by year first, by person, or location? There is no correct answer. What people want instead is a photo album with tags. The same problem applies to OOP.
- setr 5y ago> What people want instead is a photo album with tags What you really want is hierarchal tags :)
- splistud 5y agoNo, no. We want tags. Later we want hierarchal tags (with some complex workarounds to maintain some legacy thing included in the implementation).
- Zababa 5y ago> It's like trying to categorize your photos in a directory tree A problem that made me think about tags instead of categories was precisely that: I have photos that I want to organize. I started by organizing them by person with a folder for each person. But how do I handle a photo where multiple people are in it? Tags don't have this problem. Unfortunately file systems don't support tags.
- btschaegg 5y agoSilly monkeys Give them thumbs, they forge a blade And where there's one they're bound to divide it Right in two SCNR :) For everyone that doesn't know the text: I recommend listening to Tool's "Right in two". Although the text originally talks about war and strive, not programming. ;)
- jhgb 5y ago> I have this "theory" in the back of my head that trees are usually the wrong things to model thing in life but it's what come to us naturally. Have you by any chance read the relevant passages in SICP? It has some things to say about OOP ontologies.
- ferdowsi 5y agoCould you link this?
- Jtsummers 5y agohttps://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-18.html#%_sec_2.5 https://mitpress.mit.edu/sites/default/files/sicp/full-text/... Possibly that section. It's not about OOP specifically, but about type hierarchies generally.
- Zababa 5y agoI don't think I did, I didn't finish the first chapter of SICP. I'm sure that I'm not the first one to come up with this though.
- oftenwrong 5y agoThere's a great old (2005) blog post on this topic: Clay Shirky - Ontology is Overrated: Categories, Links, and Tags https://web.archive.org/web/20191117162526/http://shirky.com/writings/ontology_overrated.html https://web.archive.org/web/20191117162526/http://shirky.com...
- state_less 5y agoFolks may enjoy some of these old posts too. https://en.m.wikipedia.org/wiki/Pyrrhonism https://en.m.wikipedia.org/wiki/Pyrrhonism For example, putting regions under a cold climates category, might not make sense to someone living near the pole where they would consider the same regions to be warm climates.
- Zababa 5y agoThanks, that was a great read. Tangentially related, but I wonder what's the impact of Windows having a really bad search. Maybe people are thus relying more on the folder hierarchies, and that influences how they think?
- firethief 5y agoYou might enjoy this essay from 1965, "A City is Not a Tree": https://www.patternlanguage.com/archive/cityisnotatree.html https://www.patternlanguage.com/archive/cityisnotatree.html
- prionassembly 5y agoOh man, I have a rabbit hole for you. https://en.wikipedia.org/wiki/Rhizome_(philosophy) https://en.wikipedia.org/wiki/Rhizome_(philosophy)
- steveklabnik 5y agoHere's a near-decade old talk from me on this exact topic: https://www.youtube.com/watch?v=YfKAScYkGlk https://www.youtube.com/watch?v=YfKAScYkGlk I haven't watched this in like... a long time, so maybe I'd think it's bad now.
- prionassembly 5y agoI recommend Manuel de Landa (stylizes his name to Delanda these days), particularly “Intensive Science and Virtual Philosophy” if your flavor is Anglo style analytical philosophy. The relevant Deleuze texts (A Thousand Plateaus) can be infuriating if you’re not open to this whole other style of thinking, but Deleuze is no postmodern, he’s a realist and a materialist and sort of a science worshipper, albeit from an angle that would make Neil deGrasse Tyson start bleeding from his nose until he passed out, if he ever grokked it. Start with Delanda, probably. [googling a little, this isn’t great but isn’t bad for something readily accessible: http://dar.aucegypt.edu/bitstream/handle/10526/3534/DeLanda%27s%20ontology.pdf?sequence=1 http://dar.aucegypt.edu/bitstream/handle/10526/3534/DeLanda%... ]
- steveklabnik 5y agoHeck yeah, in full agreement with all of that.
- simiones 5y agoGo actually does implement inheritance, albeit in a roundabout sort of way: a struct can have one or more base members, and any method defined on the base members is accessible from the new struct implicitly, so they also implicitly implement any interface that was implemented by their base members. Here's an example (in the playground, because it gets a bit long): https://play.golang.org/p/TblQypAbIL2 https://play.golang.org/p/TblQypAbIL2
- throwaway894345 5y agoThat's not inheritance, it's just syntax sugar that lets a struct delegate method calls to one of its members. In other words: struct Foo {} func (f *Foo) baz() { println("Foo.baz()") } func (f *Foo) qux() { f.baz() } Given the above, `struct Bar { Foo }` is the same as: struct Bar { Foo Foo } // field called `Foo` of type `Foo` func (b Bar) baz() { b.Foo.baz() } func (b Bar) qux() { b.Foo.qux() } Since it's just syntax sugar and not inheritance, we can't put a `Bar` in a list of `Foo`s nor can we pass a `Bar` into a function that expects a `Foo`. It also means that if Bar overrides its `baz()` method like so: func (b Bar) baz() { println("Bar.baz()") } that calling `Bar.qux()` will still print "Foo.baz" and not "Bar.baz" (most languages with inheritance will print "Bar.baz", which is to say methods are virtual by default).
- tsimionescu 5y agoUsing interfaces, you easily get polymorphism as well: type FooI interface{ baz(); qux(); } foos := []FooI{&Foo{}, &Bar{}} Regarding overriding, you're right that it doesn't work out of the box. However, you can make "extensible classes" with just a little boilerplate: type Foo struct {FooI this} func (f *Foo) baz() { println("Foo.baz()") } func (f *Foo) qux() { f.this.baz() } func NewFoo() Foo { f := Foo{}; f.this = &f; return f } Now, to extend Foo: type Bar struct {Foo} func (b *Bar) baz() { println("Bar.baz()") } //the override func NewBar() Bar { b := Bar{}; b.Foo.this = &b; return b } //this would work even if we didn't override baz func main() { foo := NewFoo() bar := NewBar() foos := []FooI{&foo, &bar} for _,f := range foos { f.qux() } //prints Foo.baz(), then Bar.baz() } Since best practice even in C++ or C# or Java is to only allow inheritance for classes that are designed with it in mind, and since Go anyway has lots of other boilerplate, this shouldn't be unbearable if required. Playground link for anyone curious: https://play.golang.org/p/SKGhANuBGgB https://play.golang.org/p/SKGhANuBGgB
- jayd16 5y agoIt's the dose that makes the poison. Of course we want nice typed collection libraries and interfaces. The kids go overboard though.
- duped 5y agoI don't agree with this, and I'm personally an anti-OOP militant. Inheritance isn't the root of all evil, dynamic dispatch is. It's a remarkably powerful implementation detail but one with enormous cost, regardless of whether you're using an AoT/JIT compiled or interpreted language.
- bccdee 5y agoWhat enormous cost — slightly slower function calls? That's a pretty minor cost, and dynamic dispatch can be extremely useful sometimes.
- duped 5y agoEnormously slower function calls and zero inlining without advanced dynamic compilation, which has a number of pitfalls.
- throwaway894345 5y agoDevirtualization optimizations can turn semantically "dynamic" dispatches into static dispatches, but sometimes you really just need a dynamic dispatch. Note that a dynamic dispatch doesn't have to be anything more than a branch. Further, sometimes devirtualizing everything leads to enormous binaries and compile times. Runtime performance isn't everything, and it's typically better to opt-into devirtualization rather than to opt-out of it.
- bccdee 5y agoYou must have absurd standards if you find dynamic dispatch to be unacceptably slow. Also, yeah, not every function call needs to be inlined. One level of indirection on top of jumping into a new function isn't really much overhead at all, unless you're doing it for literally every function call.
- kaba0 5y agoAs opposed to passing function pointers everywhere, callback hell and friends? Or which language does it well in your opinion? The fact is, dynamic dispatch is needed, because not everything can be known at compile time. And as in a recent thread a HNer rightly noted (could not find it where I read it), the actually expensive thing in programming is flexibility.
- meheleventyone 5y agoThe weirdest thing is that the ECS as a way of building a game is inherently object oriented. You take a set of components and compose an object called an entity. The components on the entity define not only it's data but also it's behavior by the set of systems that act on the corresponding components. And you can take these object definitions and inherit them to add additional behavior or change the existing behavior by adding more components to the new definition. Then if you solve the entity communication conundrum with message passing and don't allow entities to directly access one another's data you basically have all the elements.
- professoretc 5y agoECS is really just OOP with dynamic multiple inheritance: an object can inherit from multiple base "classes" (with "components" providing the data and "systems" providing the code) and this inheritance structure can be changed at runtime, by adding/removing components. Everything else (struct-of-arrays vs. array-of-structs) is just low-level implementation details. When I implemented a variation on ECS for a game I'm building, I did exactly as you suggest, re, message passing: components receive and respond to messages but their implementation is hidden.
- klodolph 5y agoInheritance is one way of describing it but I don't think the term really fits. An object is composed of multiple components, and the composition can be changed at runtime. Saying that the object inherits from multiple base "classes" seems like it just makes the concept less clear. Some languages have class-based systems with inheritance: one class inherits from another, and methods implemented in the superclass can be used in the subclass. Some languages have prototype-based systems with inheritance: one object inherits from another, and methods implemented in the prototype can be used in the object. Component-based systems don't really fit my mental model of inheritance here.
- ratww 5y ago> ECS is really just OOP with dynamic multiple inheritance It's composition, not inheritance.
- colllectorof 5y ago>In all my experience with OOP, it's always been inheritance that is the root of all evil. It's not, though, and the fact that people keep repeating this meme shows that most developers don't even bother thinking about issues they face beyond superficial blamesplaining. The reason inheritance causes so many issues in languages like Java is because they are statically typed and also use classes as types[1]. Classes must be somewhere in the inheritance tree, hence you are forced into some place of that tree. To make things worse, Java has many keywords that restrict what inheritor of a class can do (private, final, etc). Inheritance is much less troublesome in, say, Smalltalk, since the language is dynamically typed. If someone expects you to implement Foo, you can (almost always) just implement its relevant methods without explicitly extending the class. Thus, a whole host of annoying scenarios simply does not occur. -- [1] BTW, this breaks one of the fundamental commandments of classic OOP: you should not depend on implementation details of an object, only on its message protocol. Obviously, it's impossible to be independent of implementation details if some library forces you to use a particular class.
- bccdee 5y agoYou're describing interface-based polymorphism, which is what go and rust use. In go, I can have a struct with methods that implements a particular interface by implementing all the methods described in that interface, but I can't inhereit from another struct. The person you're replying to called this out as a better system too.
- SPBS 5y ago> Inheritance is much less troublesome in, say, Smalltalk, since the language is dynamically typed. If someone expects you to implement Foo, you can (almost always) just implement its relevant methods without explicitly extending the class. Sorry, I don't understand this sentence. Isn't inheritance simply a way to avoid writing duplicate code? If you write the code to implement methods, isn't that not inheritance anymore?
- throwaway894345 5y agoInheritance conflates code reuse ("avoid writing duplicate code") with polymorphism (allowing for multiple different instances to implement the same interface). It also allows for trampolining method calls up and down a hierarchy (a method in a base class might call another method which might be overridden by another class in the hierarchy). Outside of OOP, we use composition for reuse and interfaces for polymorphism, and we don't trampoline method calls up and down a hierarchy because it's (probably?) always a bad idea. When we really need reuse and polymorphism, we can use both composition and interfaces, since the two are correctly orthogonal.
- lkrubner 5y agoI've previously suggested that initiation is the "root of all evil." See my essay: Object Oriented Programming Is An Expensive Disaster Which Must End: http://www.smashcompany.com/technology/object-oriented-programming-is-an-expensive-disaster-which-must-end http://www.smashcompany.com/technology/object-oriented-progr...
- lowbloodsugar 5y agoLike anything, inheritance can be used poorly, just as anyone can right poorly encapsulated code in Rust or Go. You might be able to convince me that inheritance is too dangerous for idiots, but then so is a computer, and we'd be debating where to draw the line of how smart/experienced you have to be to use it safely. This article from Noel, and the ones from Mike he links to, get under the hood and into "what is the compiler doing" and "what is the CPU doing". Down here, we're looking at how to use the features of whatever language we're using to get the results we want, rather than "how should i program oop gud".
- vlunkr 5y agoI think that it can work. IMO ActiveRecord is a perfect use of inheritance. You get tons of useful functionality out of the box, you don't have to worry about what that code looks like, and it's easy to extend or modify it. But often when I see co-workers come up with their own hierarchies, it saves maybe a couple of lines of code and makes it 5x more difficult to read, since you're jumping between parent and child classes and trying to keep track of the order of execution.