This is true. The solves for Pitfall #4. But it solves for Pitfall #4 but just explicitly being different types. That works when you really want them to be different types. For this example, I agree -- ColoredPoint and Point should not be designed as the same type.
In cases where inheritance makes more sense, the equals relationship is also easier to specify.
Reference equality's inability to survive a persistence round trip is the big killer. And sneaking in a round trip in a process that used to be fully in memory is where dragons lurk.
Sometimes you are seeing if a constructed instance equals an existing one. I would presume that is rather common. Especially with points, a basic collision system is often basically an equality check. Right?
A "point" would be a value type and you wouldn't use reference equality or inheritance for a value type.
But if this were a game you might have a "BigBoss" instance than inherits from "Enemy". If you had one boss, you'd only have one instance of it, and you would use reference equality. If you round-tripped and ended up with 2 instances of the same boss then you're already in trouble.
OK, can you go into detail why equality between Point and ColoredPoint written using composition is simpler than equality between Point and ColoredPoint written using inheritance. Specifically, how does composition make equality transitive?
A point can never equal a colored point. You can extract the point from a colored one to compare it to a point. That is, you can compare a point to the point of a colored point. But to compare a point to a colored point is essentially false, by definition.
Ah, so your point (ba-dum-ts) is that equality should just be type-invariant. That's a reasonable take, but it's a bit orthogonal to the larger conversation.
In your invariant world, is the integer 2 equal to the floating-point number 2.0. Or is 2.0 actually a composition of an integer part and a fractional part, while 2 and 2.0 are not directly comparable?