4 ms·
Liskov substitution will not save you. One of the worst cases of inheritance I've ever seen was in a hierarchy that was a perfect Liskov fit -- an even better f
by sunshowers 2y ago
Liskov substitution will not save you. One of the worst cases of inheritance I've ever seen was in a hierarchy that was a perfect Liskov fit -- an even better fit than traditional examples like "a JSON parser is a parser". See https://news.ycombinator.com/item?id=42512629 https://news.ycombinator.com/item?id=42512629.
The fundamental problem with inheritance, and one not shared by any other kind of polymorphism, is that you can make both upcalls and downcalls within the same hierarchy. No one should ever use inheritance in any long-term production use case without some way of enforcing strict discipline, ensuring that calls can only go one way -- up or down, but not both. I don't know to what extent tooling to enforce this discipline exists.
(Also I just realized I got punked by LLM slop.)
- rramadass 2y ago> No one should ever use inheritance in any long-term production use case without some way of enforcing strict discipline, ensuring that calls can only go one way -- up or down, but not both. I don't know to what extent tooling to enforce this discipline exists. Disagree with your first part. Inheritance used to express Subtyping is different from that used for Code-reuse and yet again different from that used for implementing Framework skeleton structure. You have to disambiguate them carefully when using it. See the article linked to by user "Fannon" here - https://news.ycombinator.com/item?id=42789466 https://news.ycombinator.com/item?id=42789466 As for tooling, you have to enforce the contract using pre/post/inv clauses following Meyer's DbC and also explicit documentation.
- sunshowers 2y agoThanks for that article -- I have to agree with Jacob Zimmerman in the comments to the article: > I don’t get it. I read one part of the article, think I get it, then I read a different part and what I read there doesn’t jive with what I thought I understood. And I can’t figure out how to reconcile them. --- > As for tooling, you have to enforce the contract using pre/post/inv clauses following Meyer's DbC and also explicit documentation. I think we call them asserts and type-level state machines :) I don't really believe in documentation as enough of a barrier to doing worse things. It must, at a structural level, be easier to do better things.
- rramadass 2y agoThere is no confusion if you understand that Inheritance is just a "mechanism" to express three (and maybe more) different kinds of "policies" and a single class may implement any or all of them in which case it becomes important to disambiguate which methods/functions express which "policies". There is a abstract concept and a syntactical expression of that concept which needs to be clear in one's mind. Again, asserts are just the "mechanism" to express pre/post/inv "policies" in code. Without having an understanding of pre/post/inv from the pov of Hoare Logic, merely using asserts will not give you much benefit. Documentation is quite important here. Both the above can be seen in the design of the Eiffel Language where they are integrated into proper syntactical mechanisms. Once you understand the concepts here, you can apply them explicitly even if your language does not support the needed syntax (eg. Contracts). See Bertrand Meyer's OOSC2 for details - https://bertrandmeyer.com/oosc2/ https://bertrandmeyer.com/oosc2/ Specifically "Design-by-Contract (DbC)" and "Inheritance Techniques" and "Using Inheritance well". Also relevant is my other comment here - https://news.ycombinator.com/item?id=42788947 https://news.ycombinator.com/item?id=42788947
- sunshowers 2y agoI agree that inheritance does too many things and has too many degrees of flexibility. I think other kinds of polymorphism like typeclasses don't have this issue, and are better due to that. Automation is highly preferable to documentation. I think the discussion would benefit from you concretely working through an example. What change(s) are you proposing to how inheritance is done in C++ or Java, and how would they prevent spaghetti code and nested upcalls/downcalls?
- rramadass 2y agoI am not sure that you understood what i wrote. Inheritance's flexibility is its very strength that allows you to express different concepts elegantly. Also no amount of Automation/Tooling/etc. can substitute for documentation explaining the intent behind the code. The main thing i would like to see in C++/Java/whatever is support for "Design-by-Contract" (DbC) similar to that given in Eiffel. There is already a proposal for C++; see this recent HN discussion - https://news.ycombinator.com/item?id=42131473 https://news.ycombinator.com/item?id=42131473 Basically this is a way to apply Hoare Logic to functions/methods directly in the implementation language itself. Now use this across the types/classes in a inheritance hierarchy and you can enforce the semantics that you want to express. Regarding a concrete example; I have already pointed you to Bertrand Meyer's OOSC2 and three specific chapters to read; They walk you through proper examples with explanations. Additionally also see his Applying Design By Contract paper (pdf) here - https://se.inf.ethz.ch/~meyer/publications/computer/contract.pdf https://se.inf.ethz.ch/~meyer/publications/computer/contract.... If you would like to see complete application code using a C++ OO framework i suggest creating a sample MFC app using Visual Studio C++ wizard and just looking at the generated code without adding anything of your own. It uses a Document/View architecture (a variation of MVC) where your app specific classes derive from MFC framework given classes. The framework invokes your derived methods (i.e. downcall) which can as needed call back to base's method (i.e. upcall). There is a strong coupling between the framework classes and your app specific ones by design. You can see how different usages of inheritance are implemented to give a powerful app framework; see documentation starting here - https://learn.microsoft.com/en-us/cpp/mfc/document-view-architecture?view=msvc-170 https://learn.microsoft.com/en-us/cpp/mfc/document-view-arch...
- cma 2y agoCan constructors and destructors still go counter to this idea if needed?
- sunshowers 2y agoI think values should generally only be combined into a structure at the end (no half-formed structures with null data, no calls on methods that work on half-formed structures). Destructors are more complicated, there's definitely times where you have to violate invariants that otherwise are always the case.
- Salgat 2y agoI've never seen a case where inheritance was superior to composition with a shared interface. Worst case with composition, it just returns the injected class's method directly. The beauty is that this really shines when you apply the liskov substitution principle.
- Spivak 2y agoI think Python's pattern using inheritance for mixins is probably a good candidate. But Python does have a culture of "inheritance is only for sharing code user beware if you try to use it for other things." Python's ABC classes for collections is also a good use of inheritance. Inherit from MutableMapping, implement the required methods, boom you get all the other mapping methods for free. Pydantic / dataclass inheritance is elegant for building up different collections of fields. That being said it does use codegen / metaclass hackery to do it.
- izietto 2y agoSame for Ruby, and you don't even need to inherit. You include the Enumerable module, implement next, and your instance is suddenly iterable