4 ms·
Inheritance and Subtyping
- vips7L 2y agoObject orientism is not inheritance. It is simply encapsulation and having functions attached to the data.
- Tomte 2y agoThe Common Lisp Object System disagrees with functions being attached to data.
- kazinator 2y agoIt's possible that what we can express with the Common Lisp Object System is a generalization that isn't OOP. It goes beyond OOP and includes it. I.e. OOP is a style of programm organization (readily realizable under CLOS) where we associate operations with the leftmost argument and its type.
- mrkeen 2y agoFrom one point of view, yes. Another point of view is that it's a new kind of de-encapsulation. Apparently this is a 'global' variable: int variable = 0; void foo() { variable++; } void bar() { variable++; } but this is an 'encapsulated' variable: class MyClass { int variable = 0; void foo() { variable++; } void bar() { variable++; } } You can block the top one in a code-review because "global variable", and tell the developer to fix it by passing the variable in and out of functions when needed, but I have a feeling the bottom one's getting merged even if you object on the same grounds.
- marginalia_nu 2y agoI don't think subtyping is something you should probably be using very often. I wouldn't say never, but you can go a very long time without finding a legitimate use for the feature, and your code will generally be better for it. It's a bit weird, given how these elaborate class hierarchies were touted as such a big and important feature in Java originally, but when the dust settled it turned out more often than not to complicate the code.
- deleted 2y ago[deleted]
- cobbal 2y agoPersonally I like to differentiate subclassing from conforming to an interface. Both are subtyping, but interfaces are much cleaner.
- marginalia_nu 2y agoInterface inheritance is indeed much less of a foot-gun than class inheritance, but I'll argue even interfaces are generally fairly overused. The single-class interface pattern that exists in some parts of Java-land is just bizarre.
- vips7L 2y agoI see that a lot more in C# than in Java tbh.
- marginalia_nu 2y agoIt's a cultural thing. I've seen code bases where every class was an FooImpl and had an associated FooIf, all in name of decoupling and mocking. Though it may be a trend that's dying down.
- caspper69 2y agoIt's a lesson that everyone learns eventually. You cannot eliminate complexity (although you can minimize it, to be clear). But for most things, you can shuffle the semantics around, you can paper over them in a new way, but at the end of the day, someone has to pay the piper. In my experience, complexity eliminated from one spot will invariably bubble up somewhere else in an unexpected way.
- vips7L 2y agoI remember at some time you couldn’t mock in C# without an interface or maybe it was just the popular library that required it. Java never had that requirement so at least in the codebases I’ve been in you didn’t have IBar and BarImpl everywhere.
- grey-area 2y agoGo interfaces are explicit, not implicit, the difference is more where they are defined. With subtypes the interfaces are defined with classes(an animal does this and that), with interfaces they are defined at the point of use (I accept a param that does this and that). Interesting that gosling said at one point he felt including classes (i.e. inheritance) in Java was a mistake. I feel this is one thing Go got right compared to many other languages. Turns out inheritance just isn’t very helpful.
- Jtsummers 2y ago> Go interfaces are explicit, not implicit, the difference is more where they are defined. That's not what the blog author wrote so what are you responding to? What they actually wrote: >> If a Go struct implements the same functions as an interface, it implicitly implements the interface. And that's correct except they use the term "functions" instead of "methods". But most people won't get confused by that distinction. https://go.dev/tour/methods/10 https://go.dev/tour/methods/10 - "Interfaces are implemented implicitly"
- grey-area 2y agoThey are declared explicitly and implemented implicitly. Thus the analogy the author is trying to make with magic methods in more dynamic languages like python or ruby is inaccurate and IMO misleading.
- pfdietz 2y ago> Python is the most interesting language I know of regarding inheritance. Someone doesn't know Common Lisp.
- agumonkey 2y agoSomeones doesn't know Common Lisp. that said most people don't know about things predating the 90s
- kazinator 2y agoSomeone also doesn't know Python? Isn't it the case that if we have an obj and its class has a feed method, we can call obj.feed(...) regardless of what it inherits from?
- toolslive 2y agoyou mean CLOS (Common Lisp Object System) ? There are others that are also _very_ interesting (but also pre 90s). SELF fe, was object oriented, but did not have inheritance. It used prototypes. In essence, you would create something of the same "type" by cloning the prototype. Anyway, subtyping and inheritance are similar, but not identical, concepts. Also, some languages provide traits iso protocols (and maybe even both ?). Some languages provide functors. The goal is always the same: abstract commonalities. Let's just keep it at: "it's complicated" ;) I think it's better to study the design mistakes of programming languages in a historic context. For example: C++ offers multiple inheritance. This caused the diamond problem. Java tried to fix this via interfaces. This fixed the problem, but was also a mistake as interfaces cannot provide behaviours. So Multiple inheritance is not an issue if only 1 of the parties provides state; all others can provide signatures but also behaviours.
- riffraff 2y ago> So Multiple inheritance is not an issue if only 1 of the parties provides state; all others can provide signatures but also behaviours. IIRC that's what Bertrand Meyer advised in OOSC (I think he called it "marriage of convenience" between abstract and concrete superclasses). But he also claimed the diamond problem is overstated, and (paraphrasing) it's not a deep semantic issue, but a trivial syntactic one, "simply" solved with rename-on-inherit :)
- stephantul 2y agoI think this is interesting, but I think the way Python is described is slightly wrong? In Python, you don't need to define a protocol to support duck typing, you can just implement the same behavior. Protocols don't have a function outside of static typing (they don't influence any program behavior at run-time.) I guess it depends on whether you consider duck typing to be "passes the static type checker" or "it works the same".
- alfons_foobar 2y agoTrue. Also this: "Because Animal is a Protocol, any class that defines feed() becomes an Animal" is slightly incorrect. By default, protocols are only relevant for static type analysis, but you _can_ enable runtime instance checking using the @runtime_checkable decorator. (So you can do "if isinstance(foo, Animal):...", but AFAIK this decorator comes with a performance penalty)
- knome 2y agoProtocols allow you to explicitly declare a class implements them by making them a base of the class, but do not require it. Every class that implements the specification of the protocol is usable where the protocol is specified, not only those explicitly marked with it. Python didn't make c#'s doofy mistake of requiring the programmer to explicitly annotate everything. So, not every implementer of the protocol will have isinstance be true.
- alfons_foobar 2y agoThat is correct, but the @runtime_checkable decorator (which goes on the class that defines the protocol, e.g. "Animal" in the blog's example) makes it so that any class that implements the required methods (e.g. Rabbit) also passes the isinstance test (so isinstance(Rabbit, Animal) does actually return True, even though Rabbit is not defined as a subclass of Animal, i.e. NOT class Rabbit(Animal) !) Whether it makes sense to use that is a different question, but it is possible :) See https://docs.python.org/3/library/typing.html#typing.runtime_checkable https://docs.python.org/3/library/typing.html#typing.runtime...
- sesm 2y agoAs a Java developer the author should take a look at Clojure protocols. Those are even more flexible and can be implemented for 3rd party classes or even standard library classes without wrapping.
- deleted 2y ago[deleted]
- vishnuharidas 2y agoI used Java for a while and then switched to Kotlin as my daily tool, and one interesting thing that I found was that Kotlin prevents inheritance by default, and the developer has to explicitly mark a class as "open" to allow inheritance. Eventually I stopped using inheritance and now prefer to use composition over inheritance wherever possible.
- AtlasBarfed 2y agoI haven't used kotlin. How does it help it? So the kicker in imperative OO for composition is that the class you are composing has some state / instance vars. How do you include methods that can view/update those instance vars? Sure it's easy to compose with static/pure functions. Do you enclose the instance vars in holder classes and pass those holders to the composition implementation class in it's method signature? In the end, inheritance or composition is about constructing a class /struct/whatever with a given set of expected signatures with some ideally documented guidance on any intricacies.
- caspper69 2y agoThis has been coming up a lot lately. It seems to me that a new generation of developers are simply rejecting oo-paradigms because they are oo, without an in-depth analysis. I agree with the general consensus that oo inheritance hierarchies of the past became too deep and were overwhelming, but subtyping has a place. Because interfaces are stateless, you generally cannot completely implement required interface functionality without wiring them up to each individual object, resulting in a lot of boilerplate code repetition. This is exacerbated in GUI programming, for example, where subtyping makes a lot of sense (and doesn't devolve into Animal -> Rabbit nonsense). With subtyping, you can take an object that is 95% of another object and just override where necessary. I also don't believe composition provides the same level of encapsulation that can be achieved through traditional class-based oo. There seems to be this prevailing view that oo is all about inheritance, when the truth is that oo was really about encapsulation and isolation. You only expose what you need to, and objects (whether they be classes or structs or whatever) can be built and tested without fear of code collisions or meddling from the outside view. I will concede that interfaces can and do provide a level of encapsulation, but in practice, it's just clunky. Just look at some Rust objects and the sheer number of traits that they must be aware of and implement by hand. It's piecemeal when it could be one and done. And please understand, I'm not talking about "...in the beginning there was a GObject, and that GObject bequeathed to...and..."
- vips7L 2y agoInheritance is just a tool. It’s insane to reject it outright just because someone can use it incorrectly. It does some things extremely well and better than composition.
- int_19h 2y agoThis boilerplate repetition that you refer to is only necessary when doing composition because the languages don't provide the requisite syntactic sugar for it, the way they do for inheritance. This is very unfortunate but totally fixable.
- caspper69 2y agoI'd be interested to understand how / what types of proposals exist to solve this very real ergonomic issue. As I stated in my original post, the implementations of interfaces/composition that I have used (primarily C# and Rust) are stateless, so you must always re-implement. And honestly, if Rust were to say, allow the specification of struct members in trait definitions, one could argue that their structs are really sealed/final classes, just using different keywords. I am genuinely curious, because I do believe that people have a valid point that composition does tend to lead to less object spaghetti than inheritance, even if I can't quite pinpoint why from a purely theoretical standpoint (my belief is that they're "holding it wrong").
- int_19h 2y agoSather was a very interesting example of a language that is very much "OO is the way" design, but which cleanly separates composition and inheritance: https://www.gnu.org/software/sather/docs-1.2/tutorial/inclusion.html https://www.gnu.org/software/sather/docs-1.2/tutorial/inclus... https://www.gnu.org/software/sather/docs-1.2/tutorial/abstract1259.html https://www.gnu.org/software/sather/docs-1.2/tutorial/abstra...
- viccis 2y agoPython has far more options for this if you use some of the newer typing features. For example, it now (as of 3.12) automatically detects covariance and contravariance when you are dealing with generic types, which has made subclassing much nicer for me when it comes to what a static type checker can catch or autocomplete.