10 ms·
"the real world doesn’t work that way. It really doesn’t work that way. In zoology, for example, we have penguins, birds that swim. And the bat, a mammal that f
by timr 13y ago
"the real world doesn’t work that way. It really doesn’t work that way. In zoology, for example, we have penguins, birds that swim. And the bat, a mammal that flies. And monotremes like the platypus, an animal that lays eggs but nurses its young with milk."
Former biologist here. Actually...most living things do work that way. A human ISA primate. Genetically. Functionally.
If you merely focus on outward behavior, you get to the same place that early biologists got to with what was called "morphological classification" -- you find the weird examples of convergent evolution (e.g. "OMG egg-laying mammal!"), and you're tempted to throw out the whole classification system, even though it mostly works, and the errors are merely distracting from the inherent truth of the idea (that we're all related by genetic phylogeny; we literally share implementation).
Anyway, programmers, learn from biology: when you see these kinds of errors it probably means that you're classifying things incorrectly, not that you should stop classifying altogether.
- ericHosick 13y agoWhat something is can change greatly based on context. This means how we classify them (generalize/group) can also change. An apple may be used to grow a tree, as a prop in a game, as fertilizer or consumed. That being said. Classifying things using inheritance leads to a software architecture that is difficult to change. This is especially the case if you follow the principal of don't break interface. In the real world, people can build out classification systems and easily add in edge cases when we find new ways things can be classified. In software, that could lead to a complete change in the software architecture.
- Jare 13y agoBravo sir, that was a fantastic and succinct explanation of the difference between inheritance in the real world vs in OOP.
- RyanZAG 13y agoThanks for the input - the more cross disciplinary knowledge flow the better. I like your take away in particular. The author is basically saying 'All this OOP stuff that most of the industry has been doing for 20 years? All wrong! Do it my way.' Hearing that biology still uses directed non-cyclic graphs for classification (eg, single inheritance) shows just how powerful single inheritance really is and that we maybe shouldn't be so fast in throwing it away because it doesn't match something. I think sticking to the 'age old wisdom' of using direct inheritance for IS-A relationships and composition for HAS-A is still the right way to go. It's been tested, it works, and using HAS-A for everything makes for less understandable code imo. Often you can fix an IS-A by simply refactoring your graph with a better understanding of the problem space - as it turns out biologists have done as well.
- girvo 13y agoI've been building a game engine in JavaScript as an exercise for the past few weeks. Using IS-A for my base classes and HASA composition for various needed and shareable behaviours works wonderfully, is super testable, and makes sense. Seems like the answer in this case does indeed lie somewhere in the middle of two extremes.
- derefr 13y agoIn my personal experience, I've never found a true IS-A relationship in my code. I've found lots of interfaces, though. A lot of the time, you'll think you're defining a superclass when you start writing things like "Square IS-A Shape; Rectangle IS-A Shape". But Shape will turn out to not define any common behavior, just a category you want to restrict your inputs and outputs to; and you'll want to be able to assert that anything is, in addition to whatever else it is, a Shape. So shape is an interface. A lot of the time, you'll think you're defining a superclass when you start writing things like "User IS-A Person; ProjectOwner IS-A User". But it'll turn out that you want to keep People around even when they stop being Users, and to keep Users around even when they stop being ProjectOwners. So you'll rearrange things and find that you're now asserting "User IS-A Role; ProjectOwner IS-A Role; Person HAS-MANY Roles." And Role turns out to be, again, an interface. The only example I can think of that does fit single-inheritance is when modelling objects that directly express a genealogy. For example, GithubForkOfProjectA IS-A ProjectA, or Ubuntu IS-A Debian. But these aren't typically things you'd express as types; they're just instances (of, respectively, GithubProject and LinuxDistribution.) Each instance HAS-A clonal-parent-instance. I guess there's one possibly-practical use of inheritance which I've nearly implemented myself: if you force your database schema migrations to always follow the Open-Closed Principle, and you want to migrate the rows of a table as you encounter them to avoid taking the DB offline, then you could have two separate models for a Foo table, FooV2 and FooV3, where "FooV3 IS-A FooV2". Each row has a version column, and is materialized as the model corresponding to that version. Your code that expected FooV2s would then be satisfied if it was passed a FooV3. Does anyone actually do this, though? I don't just mean "row-by-row migrations", I mean the "with two models, one version inheriting from the other" part. And, if so, what do you do when you make a change to a model that doesn't obey the Open-Closed Principle: where FooV3s break the FooV2 contract?
- edderly 13y agoThe comparison of real life biology and the software problem is superficial in the first place. No one is releasing ‘Biological life forms 2.0’ and worrying about whether penguins stop working. The problem is that writing reusable software is hard, inheritance is a really easy way to introduce reusable code into your software but a bad abstraction or base class has viral effects.
- mcv 13y agoYeah, that the penguins and bats example would be a problem for class hierarchies is totally silly. Those were the exact examples that were originally used to teach me about class hierarchies; you can _override_ stuff like that in subclasses. That was the entire point.
- icambron 13y agoI think there's some confusion about the analogy being drawn here. Biological classification is, like you said, a question of shared implementations. That's useful and it works especially well in biology because organisms are largely built through specialization; i.e. it really is a tree, where ancestry groups organisms by shared genetic material. But to begin with, we're not classifying things; we're building them. Software is designed; you don't (or at least shouldn't), construct your programming abstractions by progressively mutating the different parts of your code. Nature had to reimplement flying for the bat, but you don't have to! You get to build a bat by strapping wings onto a rat, and you know how to build wings because you already did it for birds. But the hierarchy system doesn't let you do that; you'll have to do what nature did and completely reimplement flying for the Rodent subclass of Mammal, since Bird is nowhere near it in your hierarchy diagram. That's the essence of the author's point, as I read it. I suspect Raganwald would probably go a lot further than an OOP+mixin approach (and there is no shortage of good alternatives), but the simplest way around this kind of thing is something like this piece of Ruby: class Bat include Flying include Rodent include Echolocation end You get the idea, and it's pretty easy to port to JS. It also lets you build the griffin and the centaur, because the software doesn't have to follow nature's rules about how to derive one thing for another. Constraining your code's structure to evolution's is-a model is a straight jacket. So that's implementation, but what about interface? We actually do care about outward behavior, and making our interfaces reflect that is important. Morphological classification may not tell you much about how a bat works inside and what other species a bat is evolutionarily close to, but it remains the case that if you want to know what a bat does, flying is a big part of that. What if I want to know whether a given animal can get over my fence? How do I ask if a creature can fly? I don't care at all that bats are closely related to mice because they're acting nothing like mice when zooming over my fence. Do I really add canFly() to some Animal superclass and have flying creatures override it? How many features will I detect that way? But with our mixins, we can just ask if it includes Flying, because the structure of our code captures that. Codifying the structure of your interface into the structure of your classes is part of the point of OOP, but it turns out that interfaces don't fit a simple inheritance mold. So you need more powerful tools to describe that structure. This issue comes up enough in gaming that the no-hierarchy-lots-of-mixins approach has a name: the entity-component model. I bring this up not because I think the point applies differently to games but because it's a domain where you might actually build a bat. Finally, how far would we really be willing to take biology's classification-by-implementation philosophy, even if classifying pieces of code were really our goal? We could end up with superclasses like UsesAStar and HashSet. Not really the point of all of this, right? Long story short: I agree with the article and think the biology doesn't contradict it, but think we should probably stop using evolution as a conceptual model for how we organize our code.
- deviltreh 13y agoclass Penguin : Bird{ override void Fly(){ throw new InvalidOperationException("can't fly"); } }
- setrofim_ 13y agoBut now you can't assume that Birds can Fly. You have to catch InvalidOperationException on every invocation. And what's special about Fly? What above other Bird methods, surely the same can be done to all of them? Now you can't assume anything about a Bird. So what good is the Bird abstraction then, if you can't make any assumptions about it? This is breaking the Liskov Substitution Principle[1]. By saying that Penguin is a subclass of Bird, you're implying that I should be able to stick a Penguin into any code that expects a Bird, that code will just work, without it having to care about the particularity of that Bird actually being a Penguin. [1] http://en.wikipedia.org/wiki/Liskov_substitution_principle http://en.wikipedia.org/wiki/Liskov_substitution_principle [edit: grammar]
- nslamberth 13y agoNit picking here, but birds as a biological class are defined as having wings as opposed to being able to fly. So the bird abstraction allows you to assume a bird has wings, and if it has wings it is likely able to fly (but not guaranteed). Overall agreed, though. This ties into timr's point that if there are errors in your classifications, then your class hierarchy is incorrect.
- setrofim_ 13y agoWell, yes, indeed. It depends on the purpose of the abstraction. It may well be that Fly() should not be a method of Bird (maybe we need a FlyingBird subclass, or, even better, a Flying mix-in). My objection was to the "solution" of throwing InvalidOperation, not in support of the assumption that birds can necessarily fly (note the capitalization in may initial remark).
- deleted 13y ago[deleted]
- bztzt 13y agoBut that's an example of why inheritance-as-subtyping is dubious - sharing implementation doesn't -> sharing behavior, and for subtyping common behavior that can be relied on by clients is what we want.
- iopq 13y agoYes, but does Human inherit Neanderthal? If we don't know this beforehand, how do we implement both classes?
- ascotan 13y agoThere is a classification system, but it's a neat 'cubby-hole' approach to systems biology. The reality is that not everything has a clean parent-child relationship. For example, humans have the ABO blood group system. If you have A type blood you are not a 'subclass' of a human with no blood type. All ridiculous discussions of biology aside, having a class based hierarchy is a maintenance issue in comparison to designing classes through compositing. There are 2 ways to design a 'cheeseburger': a) meat sandwich is a super class of burger which is a super class cheeseburger b) meat sandwich which contains, bun, cheese, patty lettuce B is a more maintainable system and allows for better class flexibility in the design. It avoids having complex bugs where 'meat sandwich' changes some internals and breaks something down the hierarchy chain in some subtle way (for example in releasing a access to a critical resource).
- lightblade 13y agoSure, if we take that analogy further, what happens during environment change (aka requirement change)? With biology you get extinction, a whole class of organisms dies off. You then create new classes from some root branch. Class based code base works similarly. You end up deleting a bunch of classes then create new ones. I think we can do better than this.
- einhverfr 13y agoI think the fundamental problem though is that there are plenty of areas in biology where this breaks down on a practical level. The problem is actually a nomological problem, which deals with the limits of ontologies. Do "species" exist as such? Or are the limits which we place on them projected by us? Obviously a bat is not a dolphin, but where do you draw the line between species and subspecies, between one species and another closely related one, or between one genus and another closely related one? These turn out to be remarkably tricky questions which is why Linnean classification is not as stable as we would like to believe..... It mostly works, but only to some extent, which is the problem.
- mattfenwick 13y agoAnd with the limits not only of ontologies, but of models in general. All models are wrong. Some are useful. As you pointed out, there is no such thing as a species in the real world -- it's just in the model that there's a species. The model has been so successful that, unfortunately, it's treated as "true" by many people, which means that 1) they don't understand its limitations, and 2) they don't work to improve it. +1, I'm glad you mentioned this.
- einhverfr 13y agoThe example that comes to my mind is Rosa gallica vs Rosa alba vs. Rosa damascena. Trying to understand why these are species rather than subspecies, given the ease of interbreeding (over many generations and hundreds of years), leads to very big headaches.
- briantakita 13y agoTo properly model all species with inheritence, species have to inherit from ancestors in the lineage. A Human IsA Primate because proto-primates evolved before humans did. The hierarchy is a graph with a generation (time) axis. The dissonance comes in with timeless ontologies. Ontologies whose members don't share a common ancestor. Rather, the members share traits. Inheritance works in some cases, but not in all. Once it stops supporting all cases, it causes unnecessary complexity to the model.
- lifeisstillgood 13y agoIf I understand it correctly, the author is saying "Is-a" is a poor often broken means of building an Eco-system. And that interfaces are more correct - "has-a" is more flexible and useful. In my limited understanding of biology this works fantastically well. For a platypus, "Is-a" is ridiculously broken, and one would not want to try and reproduce the underlying source code. However for classifying and interacting with the platypus "has-a" warm blood, "has-a" egg-laying-thing (#) "has-a" receptor for x is all we care about. I suspect that is-a works fine at the cellular level and tends to break higher up - and that is pretty much my experience - in software we want to build Eco-systems - and forget that an individual humans body is more an Eco-system than a single machine. interesting food for thought (#) what I said about limited biology
- mattfenwick 13y agoAll models are wrong. Some are useful. You're confusing the model with reality. The classification system is simply a model that describes many things correctly, and many other things incorrectly. If you feel that there is inherent truth of the idea, then that probably means that it's a really good model. But it's still just a model. For example, you said that a human ISA primate. But that's only in the model. Outside of the model, there is no such thing as a primate. Reality is far more complicated, and "human ISA primate" misses many corner cases. You also said we literally share implementation, which, again, may be useful but isn't quite true, whether one is talking about DNA, epigenetics, or macroscopic properties. But that's okay -- it's still a good model, because it's useful and simple and easy to understand and works much of the time. We just have to be careful not to forget its limitations and applicability.
- isleyaardvark 13y agoIt is pretty regularly pointed out in discussions of evolution, that if someone were to try and "intelligently design" lifeforms, they would be better off mixing and matching components instead of trying to pile on with inheritance like you find in nature. (That's how you get a blind spot in the human eye, or the giraffe's crazy laryngeal nerve.)
- metaphorm 13y agowhat is the programming equivalent of "morphological classification"? what is the morphology of a class? I would argue that it is the interface. That is the part that determines the form that other programs interact with. that's the part that actually matters for object oriented design. so while you're right that in biology morphological classification is a distraction that belies the existence of deeper relationships, in computing morphological classification is actually the most important part and what we should focus on.
- copergi 13y agoMostly is as good as not at all though. Yes, you should stop using class hierarchies. Composition is the path to salvation.