5 ms·
> Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called compositio
by titzer 2y ago
> Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives.
Bah. There are completely legitimate uses of inheritance where it's a really great fit. I think you'll find that being dogmatic about avoiding a programming pattern will eventually get you twisted up in other ways.
Inheritance can be used in a couple of ways that achieve a very-specific kind of code reuse. While I went through the early 2000's Java hype cycle with interfaces and factories and builders and double-dispatch and visitors everywhere, I went through a period where I hated most of that crap and swore to never use the visitor pattern again.
But hey, within the past two years I found an unbeatable use case where the visitor pattern absolutely rocks (it's there: https://github.com/titzer/wizard-engine/blob/master/src/util/BytecodeVisitor.v3 https://github.com/titzer/wizard-engine/blob/master/src/util...). If you can come up with another way by which you can deal with 550 different kinds of animals (the Wasm instructions) and inherit the logic for 545 and just override the logic for 5 of them, then be my guest. (And yes, you can use ADTs and pattern-matching, which I do, liberally--but the specifics of how immediates and their types are encoded and decoded just simply cannot be replicated with as little code as the visitor pattern).
So don't completely swear off inheritance. It's like saying you'll never use a butter knife because you only do pocket knives. After all, butter knives are dull and not good for anything but butter.
- bedobi 2y agoI happily admit it's more than possible to come up with examples that make inheritance shine. After all, that's what the authors of these books and articles do. But most of them put the cart before the horse (deliberately design a "problem" that inheritance "solves") and don't seriously evaluate pros and cons or even consider alternatives. Even then, some of the examples might be legitimate, and what you're referring to might be a case of one. (though I doubt there's no equally elegant and succinct way to do it without inheritance) But none of that changes the fact that inheritance absolutely shouldn't be the default goto solution for modeling any domain it has become (and we are taught to understand it as) or that it's exceedingly uncommon to come across situations like yours where you have 500+ shared cases of behavior and you only want to "override" 5 or that inheritance is overwhelmingly used NOT for such niche edge cases but as a default tool to model even the most trivial relationships, with zero justification or consideration
- titzer 2y agoI agree that examples matter a lot, and for some reason a lot of introductory OO stuff has really bad examples. Like the whole Person/Employee/Employer/Manager dark pattern. In no sane world would a person's current role be tied to their identity--how do you model a person being promoted? They suddenly move from being an employee to a manager...or maybe they start their own business and lose their job? And who's modeling these people and what for? That's never shown. Are we the bank? The IRS? An insurance company? Because all of these have a lot of other data modeling to do, and how you represent the identities of people will be wrapped up in that. E.g.--maybe a person is both an employee and a client at the same time? It's all bonkers to try to use inheritance and subtyping and interfaces for that. Algebraic data types excel at data modeling. It's like their killer app. And then OO people trot out these atrocious data modeling examples which functional languages can do way better. It's a lot of confusion all around. You gotta program in a lot of different paradigms to see this.
- disqard 2y agoI love your concrete examples! Thanks for sharing the pointer to your wasm engine. Is that part of a course you teach, or something born out of an auto-didactic pursuit?
- rramadass 2y agoUser "titzer" is Ben Titzer; co-founder of WebAssembly - https://s3d.cmu.edu/people/core-faculty/titzer-ben.html https://s3d.cmu.edu/people/core-faculty/titzer-ben.html
- disqard 2y agoTIL, thank you!
- rramadass 2y agoYeah, there are some real good experts on various subjects here on HN. One thing i would recommend is to contact anybody directly if needed (through their email ids in their profile or otherwise) with any questions you might have. That way you can have a longer discussion and/or learn more on specific subjects. Most people are willing to help generously when approached in a knowledge-seeking manner. I always look at HN threads/discussion as merely giving me an idea of different concepts/subjects and ask for pointers to more knowledge either books/papers or experts. Hopefully i also do the same with my comments thus helping the overall s/n ratio of this site.
- vjerancrnjak 2y agoIf you can use functions in the same way objects are used, there’s no need for visitor objects. There’s a reason why everything is a Lisp. All of the patterns are obvious with its primitives, while higher level primitives like classes, interfaces hide that there’s data and there’s behavior/effects.
- tsimionescu 2y agoVisitor objects are needed when you want, at runtime, to decide what code to execute based on the types of two parameters of a function (regular OOP virtual dispatch can only do this based on the type of one argument, the one before the dot). While you can model this in different ways, there is nothing in "plain" Lisp (say, R7RS Scheme) that makes this particularly simple. Common Lisp does have a nicer solution to this, in the form of CLOS generic functions. In CLOS, methods are defined based on the classes of all arguments, not just the first one like in traditional object systems. Combined with inheritance, you can implement the whole thing with the minimal amount of code. But it's still an OOP system designed specifically for this.
- kazinator 2y agoThe Visitor Pattern is one of the ones that actually does not go away when you have CLOS. That is to say the traversal and visitation part of it doesn't go away, just all the boiler plate around simulating the double dispatch, like needing two methods: accept and visit and whatnot. Like say we want to visit the elements of a list, which are objects, and involve them with a visiting object: (mapcar (lambda (elem) (generic-fun visitor elem)) obj-list) We write all the method specializations of generic-fun for all combinations of visitor and element type we need and that's it. Importantly, the traversal function doesn't have to know anything about the visitor stuff. Here we have mapcar, which dates back to before object orientation.
- tsimionescu 2y agoThe traversal is not really part of the visitor pattern. The element.accept(visitor) function together with the visitor.visitElementType(element) are the identifying part of the visitor pattern, and they completely disappear with CLOS. A classic example is different parsers for the same set of expression types. The expressions likely form a tree, you may not need a list of expressions at all, so no mapcar.