I was a bit backward with my language. What I was trying to get at is that many OO languages encourage you to add fields in derived classes. This makes the derived class a supertype, being a larger set that fully includes the set of its parent class. You’re adding elements to a product type. But this often breaks substitutability, so to conflate class inheritance (a useful concept) with subtyping (another useful concept) is fraught with problems.
> This makes the derived class a supertype, being a larger set that fully includes the set of its parent class.
This is not how "supertype" is typically defined in the literature. I'm not sure what "set of its parent class" means, but I believe the way to look at this is to consider the universe of all possible objects.
In that universe, you will have some objects that are instances of the derived class. Each of those is also an instance of the base class. You may also have instances of the base class that are not instances of the derived class (they may be straight instances of the base, or be instances of some other sibling derived class).
That means the set of instances of the base class completely includes the set of instances of the derived class (since every derived is also a base). Hence, the base class is a supertype. Conversely, since the set of instances of the derived class may not include some instances of the base class, it is a subtype.
> You’re adding elements to a product type.
I don't think you can cleanly map classes to tuple types. Consider a derived class that adds no fields. In that case, it's not an identical type to its base class, but it would be an identical product type.
Yes and no. I think the poster does make an interesting point.
If the parent class is instantiable - which is a code smell already but also very common and even encouraged by some OO evangelizing[1], as is the case for the "colored point" example - then he's right, the derived class which adds fields does admit more values than the parent class does (excluding child values).
[1] encouraged by the commonly heard advice: "if you don't like exactly what the class does, then derive from it and supply your own customizations". Ie. this is the use of inheritance for ad-hoc customization, as opposed to a design-first approach which would have the parent be abstract (which has its own problems of course such as requiring great foresight).
Range and capability are separate concepts. That’s what I’m getting at—conflating them is problematic, not least because it’s trying to statically specify dynamic behaviour.
Here’s a simple example. A 2D vector (x:ℝ × y:ℝ) is a subtype of a 3D vector (x:ℝ × y:ℝ × z:ℝ). The range of the 3D vector includes that of the 2D one, but there is no sane way to make one a subclass of the other, due to differences in behaviour—magnitude, for instance.
> Here’s a simple example. A 2D vector (x:ℝ × y:ℝ) is a subtype of a 3D vector (x:ℝ × y:ℝ × z:ℝ).
Again, I think that's backwards. Every 3D vector is also a 2D vector if you ignore its z coordinate, so 3D vectors are a subtype of 2D vectors (assuming some type system that has subtyping between tuples of fewer fields).
You could implement this by having 3D vector subclass 2D, but as you note that's fraught with peril. But that's because substitutability requires meaningful semantic behavior and not just matching signatures. Just because two objects can both "foo" doesn't mean they are substitutable in a pragmatic sense. They have to do the appropriate thing when you foo them.