6 ms·
A couple of other comments have argued that "ontological inheritance" and "abstract data type inheritance" are actually the same thing: > This is because Squar
by panic 9y ago
A couple of other comments have argued that "ontological inheritance" and "abstract data type inheritance" are actually the same thing:
> This is because Squares are Liskov substitutable for Rectangles... which is because Squares are, platonically, a kind of Rectangle.
> If the type system is sound and expressive enough, ontological inheritance ( this thing is a specific variety of that thing) and abstract data type inheritance (this thing behaves in all the ways that thing does and has this behaviour) should be essentially the same thing.
The difference is that ontology exists in the mind of the programmer, but Liskov substitutability is a property of the program itself. No matter how you model it, a Square is "platonically a kind of" Rectangle. But in order for them to be Liskov substitutable, you have to model them in compatible ways. If my Square class only has a sideLength method, I can't substitute it for a Rectangle.
This simple example may seem silly, but as models get more complex, it becomes harder to make them compatible, even if one modeled class of objects seems like "platonically a kind of" another class. You see this kind of thing all the time in real-world systems. For example, in a UI system, an "OpenGL View" is conceptually a kind of "View", and this relationship is modeled by making OpenGLView a subclass of View. Normal 2D drawing doesn't work in this kind of view, however, so it's not Liskov substitutable.
- belorn 9y agoI always find that the common example of Rectangles and Squares leads me to a different conclusion. It assume that the best way to go about is to only have a single sideLength method, but from a data structure perspective it seems more obvious that the Squares class substitute instead the validation method by adding constraints that a valid square only exist when both sides are equal. Rectangle class must already have a validation method that check that each side is greater than zero, so it seem like the obvious place to define a square. Any additional methods or properties like sideLength would just be added convenience and optimization to access the width and height at the same time, but which is not required for anyone substituting one for the other.
- skywhopper 9y agoAgreed. In fact I would expect the Square subclass to implement width= and height= so that they each actually set both internal values.
- niklasjansson 9y agoSo this behavior is reasonable? Rectangle r=getRectangle(); r.width=1; Assert(1, r.width); //passes r.height=2; Assert(1, r.width); //fails or passes depending on subclass
- Paul_Dirac 9y agoThe problem is that the square is a rectangle in the mathematical sense: a rectangle with ANY additional constraints while code which uses a rectangle expects a rectangle with NO further constraints. I believe the right choice is to have an AbstractRectangle class representing the rectangle with any further constraints and Rectangle class representing the rectangle with no further constraints.
- jdmichal 9y agoAs niklasjansson points out, this solution breaks Liskov substitution. Squares programmed in such a way are no longer substitutable for rectangles. Breaking Liskov substitution is a great way to hide bugs and make your codebase extremely hard to reason about, due to the fact that every subtype can have drastically different behavior than what's defined by the parent type.
- deleted 9y ago[deleted]
- voidifremoved 9y agoBut then both are just a kind of polygon, with an arbitrary number of sides of arbitrary length. At which point you really want a variable length list of Sides, each with a length property. Or you want a series of points with relative coordinates, making the sides implicit. Or or or... What I take from it is that there is no one true object model, there is no universally "correct" way of solving the problem. An evolving understanding of the problem leads to an evolving solution that places different priorities on different attributes. Is there any value provided to the system by making Square a specialisation of Rectangle? Is there value in being able to express a Square without a redundant attribute value? What is the tradeoff? And so on.
- unclebucknasty 9y agoIt's interesting because it highlights that if a thing has so many properties describing what it is, then at a certain point it's not a thing at all, but a collection of characteristics that could be tweaked to describe anything. At that point, the idea of behaviors is rendered void, as the universe of possible behaviors is just too large. So, I think that's the guiding principle in your suggestion to consider what value a given design provides to the system: that is, start by modelling the behaviors your system requires, then create an object hiearchy that reflects those behaviors most concisely. This is probably our intent, but we may be derailed by too little respect for YAGNI.
- dwaltrip 9y agoThis is a very insightful point. These models and concepts are all human created abstractions. They contain elements of arbitrariness, describe different levels of detail, focus in on certain aspects or dynamic, leave out other information, and so on. The skilled software developer must determine how useful and well-suited any particular abstraction is for the actual problem at hand, and judiciously move forward that. As understanding of the problem domain grows, the abstractions should be updated and discarded to reflect this. Pragmatism is key -- what does the system actually need to do? Supposed platonic ideals of some conceptual object are often a wild goose chase. Reality is complex. We must embrace it.
- milesrout 9y agoThe thing is, rectangles are a kind of square, and squares are a kind of rectangle, from different perspectives.
- your-nanny 9y agoFrom what perspective is a rectangle a kind of square?
- balfirevic 9y agoFrom a write-only perspective. If you imagine mutating operations that square might have (for example setSideLength) rectangles can satisfy them perfectly well.
- deleted 9y ago[deleted]
- jdmichal 9y agoYes! The thing that ends up breaking in the squares and rectangles examples is mutability. Remember that math things are immutable by default. This thing is a square, and by definition also a rectangle, and since its properties and identity are immutable, that will always be true. However, programming takes those immutable concepts and tends to make them mutable. So now we have a rectangle, and we can change its identity by, say, scaling its width. And it's obvious that scaling just the width of a square will make it no longer a square. So now a square cannot support the same operations that a rectangle can. So now a square is no longer Liskov-substitutable for a rectangle.
- smaddox 9y agoExcellent point! I think it's fair to say that substitutability can only be satisfied (in general) for a fixed set of operations. This is why I believe Haskell's (and by extension Rust's) approach to bounded ad-hoc polymorphism is the best that I've seen in a production language. Going beyond Haskell, Rust also allows you to choose static dispatch, making bounded polymorphism zero cost. Dynamic dispatch is still available, when desired or needed (but it's almost never needed).
- jdmichal 9y agoI'm a huge fan of the Rust and Haskell systems for exactly this reason. I sometimes play with the idea of a language that has that kind of type system, but with a more forgiving environment Rust or Haskell. Something like C#, but swap out the type system to feel more like Rust.
- gugagore 9y agoPerfect! This is the covariant/contravariant/invariant distinction, right? "Read-only data types (sources) can be covariant; write-only data types (sinks) can be contravariant. Mutable data types which act as both sources and sinks should be invariant." from https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) https://en.wikipedia.org/wiki/Covariance_and_contravariance_...
- 9y ago
- mannykannot 9y agoThis is far from a silly example; it gets right to the core of the matter. As belorn mentioned, a square is a constrained rectangle, and inheritance by extension cannot represent this relationship.
- jdmichal 9y agoInheritance can represent this relationship, as long as all the defined operations in the rectangle are covariant. Because squares are covariant (more constrained) than rectangles. This generally means that the rectangle type must be immutable.
- mannykannot 9y agoIndeed, but unfortunately these complications almost never comes up when programming in current mainstream object-oriented languages is taught. Usually, the take-home lesson is, "if you have a type hierarchy, use inheritance - it's simple."
- jdmichal 9y agoYou are absolutely right that there is an education problem. I got lucky by randomly deciding to take the OOP principles elective. It's still not a required course at my school, though I know from recruiting trips that other schools include it. I think there's a place for a language that separates inheritance from subtyping. A lot of misuse of inheritance that I see comes from inheriting for functionality instead of subtyping.
- mncharity 9y ago> square is a constrained rectangle, and inheritance by extension cannot represent this relationship A hypothetical nice type system, providing a push-out (path independent) lattice of theories/algebras (eg triples of types, operators, and laws) can represent this relationship by extension. It's just adding a law. That we don't have such a type system available, is I suggest, perhaps the most crippling characteristic of our current tooling. But given the levels of type system research funding over the last decades, it's rather a self-inflicted injury. :/
- mncharity 9y ago> but as models get more complex, it becomes harder to make them compatible One thing that helps, is locally-scoped facades, like ruby's refinements.[1] You don't need complete model substitutability, only locally-sufficient substitutability. [1] https://ruby-doc.org/core-2.5.0/doc/syntax/refinements_rdoc.html https://ruby-doc.org/core-2.5.0/doc/syntax/refinements_rdoc....