¿Qué está * tan * mal con la herencia de clase de caso?

Mientras buscaba algo más, por mera coincidencia, tropecé con algunos comentarios sobre cómo es la herencia de la clase de caso diabólico. Había algo llamado ProductN , miserables y reyes, elfos y magos, y cómo se pierde algún tipo de propiedad muy deseable con la herencia de las clases de casos. Entonces, ¿qué tiene de malo la herencia de la clase de caso?

Una palabra: igualdad

case clases de case vienen con una implementación provista de equals y hashCode . La relación de equivalencia, conocida como equals funciona de esta manera (es decir, debe tener las siguientes propiedades):

  1. Para todo x ; x equals x es true (reflexivo)
  2. Para x , y , z ; si x equals y y y equals z entonces x equals z (transitivo)
  3. Para x , y ; si x equals y entonces y equals x (simétrica)

Tan pronto como permita la igualdad dentro de una jerarquía de herencia, puede dividir 2 y 3. Esto se demuestra trivialmente con el siguiente ejemplo:

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

Entonces nosotros tenemos:

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

Pero no

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

Podría argumentar que todas las jerarquías de clase pueden tener este problema, y ​​esto es cierto. Pero las clases de casos existen específicamente para simplificar la igualdad desde la perspectiva de un desarrollador (entre otras razones), por lo que hacer que se comporten de forma no intuitiva sería la definición de un objective propio.


También había otras razones; especialmente el hecho de que la copy no funcionó como se esperaba y la interacción con el patrón de coincidencia .

Eso no es cierto en general. Y esto es peor que la mentira.

Como mencionó aepurniet en cualquier caso, el sucesor de clase que constriñe un área de definición debe redefinir la igualdad porque la coincidencia de patrones debe funcionar exactamente como igualdad (si intenta hacer coincidir el Point como ColoredPoint , no coincidirá ya que el color no existe).

Eso da una idea de cómo podría implementarse la igualdad de la jerarquía de clases de casos.

 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 

Eventualmente, es posible satisfacer los requisitos de la relación de igualdad incluso para el sucesor de la clase de caso (sin anulación de la igualdad).

 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) = ???