What is *so* wrong with case class inheritance?

ScalaInheritanceCase Class

Scala Problem Overview


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 ?

Scala Solutions


Solution 1 - Scala

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.

Solution 2 - Scala

That is not overall true. And this is worse than lie.

As mentioned by aepurniet in any case class successor which constricts a definition area must redefine the equality because pattern matching must work exactly as equality (if try to match Point as ColoredPoint then it will not matched since color is not exists).

That give understanding to how the equality of case class hierarchy could be implemented.

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

Point(0, 0) equals ColoredPoint(0, 0, RED)  // false
Point(0, 0) equals ColoredPoint(0, 0, null) // true

ColoredPoint(0, 0, RED) equals Point(0, 0)  // false
ColoredPoint(0, 0, null) equals Point(0, 0) // true

Eventually it is possible to satisfy requirements of the equality relation even for case class successor (without overriding of equality).

case class ColoredPoint(x: Int, y: Int, c: String)
class RedPoint(x: Int, y: Int) extends ColoredPoint(x, y, "red")
class GreenPoint(x: Int, y: Int) extends ColoredPoint(x, y, "green")

val colored = ColoredPoint(0, 0, "red")
val red1 = new RedPoint(0, 0)
val red2 = new RedPoint(0, 0)
val green = new GreenPoint(0, 0)

red1 equals colored // true
red2 equals colored // true
red1 equals red2 // true

colored equals green // false
red1 equals green // false
red2 equals green // false

def foo(p: GreenPoint) = ???

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAshkan Kh. NazaryView Question on Stackoverflow
Solution 1 - Scalaoxbow_lakesView Answer on Stackoverflow
Solution 2 - Scalauser1303559View Answer on Stackoverflow