What is *so* wrong with case class inheritance?

13,844

One word: equality

case classes come with a supplied implementation of equals and hashCode. The equivalence relation, known as equals works like this (i.e. must have the following properties):

  1. For all x; x equals x is true (reflexive)
  2. For x, y, z; if x equals y and y equals z then x equals z (transitive)
  3. For x, y; if x equals y then y equals x (symmetric)

As soon as you allow for equality within an inheritance hierarchy you can break 2 and 3. this is trivially demonstrated by the following example:

case class Point(x: Int, y: Int)
case class ColoredPoint(x: Int, y: Int, c: Color) extends Point(x, y) 

Then we have:

Point(0, 0) equals ColoredPoint(0, 0, RED)

But not

ColoredPoint(0, 0, RED) equals Point(0, 0)

You might argue that all class hierarchies may have this problem, and this is true. But case classes exist specifically to simplify equality from a developer's perspective (among other reasons), so having them behave non-intuitively would be the definition of an own goal!


There were other reasons as well; notably the fact that copy did not work as expected and interaction with the pattern matcher.

Share:
13,844
Ashkan Kh. Nazary
Author by

Ashkan Kh. Nazary

Scala, Functional Effects, Type Systems

Updated on June 07, 2022

Comments

  • Ashkan Kh. Nazary
    Ashkan Kh. Nazary about 2 years

    While looking for something else, quite out of mere coincidence I stumbled upon few comments about how diabolical case class inheritance is. There was this thing called ProductN , wretches and kings, elves and wizards and how some kind of a very desirable property is lost with case classes inheritance. So what is so wrong with case class inheritance ?

  • Ashkan Kh. Nazary
    Ashkan Kh. Nazary about 12 years
    And what about a little elaboration :) ?
  • Luigi Plinge
    Luigi Plinge about 12 years
    It seems like such an asymmetric equivalence would be a useful thing in the OO paradigm, in the same way that at the type level a ColoredPoint is-a Point but not vice-versa. Might have to call it something other than equals though... maybe subEquals?
  • Dan Burton
    Dan Burton about 12 years
    @LuigiPlinge perhaps canReplace, supersedes, specifies, or overrides for the reverse relationship? Anything to indicate the >=-ness (or >: if you like) of it. It seems much easier for me to name it in terms of >= rather than <=.
  • Luigi Plinge
    Luigi Plinge about 12 years
    On second thoughts, such a thing would be tricky (impossible?) to implement due to the possibility of upcasting, so maybe it's not such a great idea
  • expert
    expert almost 11 years
    This example is not compilable in 2.10.2
  • aepurniet
    aepurniet about 9 years
    a generic equals is trivially easy to implement that would satisfy equality, make the class a member of the comparison. the copy thing looks like its just a bug, and interaction with the pattern matcher should work, as it does for non case class hierarchies.
  • Lodewijk Bogaards
    Lodewijk Bogaards almost 9 years
    Exactly, ColoredPoint.class != Point.class?
  • Alexey Romanov
    Alexey Romanov almost 8 years
    case class only gets equals when none of its parents override it, so in this case ColoredPoint would use Point's equals/hashCode (I don't know if this was already the case in 2012) which is symmetric (and reflective and transitive). You could argue ColoredPoint(0, 0, RED) == ColoredPoint(0, 0, GREEN) is unintuitive, and I'd agree, but the problem is not with case class inheritance: you have exactly the same problem if Point is a non-case class overriding equals. copy is more of a problem.
  • Admin
    Admin about 7 years
    Would anybody extend this answer by providing the best practice solution to the ColoredPoint problem, please?