При поиске чего-то другого, совершенно из простого совпадения, я наткнулся на несколько комментариев о том, как наследуется дьявольское наследование класса случаев. Эта вещь называлась ProductN
, негодяями и королями, эльфами и волшебниками и как теряется какое-то очень желательное свойство с наследованием классов случаев. Так что же случилось с наследованием класса case?
Что такое * так * неправильно с наследованием класса case?
Ответ 1
Одно слово: равенство
Классы case
имеют поставляемую реализацию equals
и hashCode
. Отношение эквивалентности, известное как equals
, работает так (то есть должно иметь следующие свойства):
- Для всех
x
;x equals x
true
(рефлексивный) - Для
x
,y
,z
; еслиx equals y
иy equals z
, тоx equals z
(переходный) - Для
x
,y
; еслиx equals y
, тоy equals x
(симметричный)
Как только вы разрешите равенство внутри иерархии наследования, вы можете разбить 2 и 3. Это тривиально продемонстрировано в следующем примере:
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)
Но не
ColoredPoint(0, 0, RED) equals Point(0, 0)
Вы можете утверждать, что все иерархии классов могут иметь эту проблему, и это верно. Но классы случаев существуют специально для упрощения равенства с точки зрения разработчика (среди других причин), поэтому, если они ведут себя неинтуитивно, это будет определение собственной цели!
Были и другие причины; в частности, тот факт, что copy
не работает должным образом и взаимодействие с шаблоном сопоставления.
Ответ 2
Это не так. И это хуже, чем ложь.
Как упоминалось aepurniet в любом случае, преемник класса, который сужает область определения, должен переопределять это равенство, поскольку сопоставление шаблонов должно работать точно так же, как и равенство (если попытаться сопоставить Point
как ColoredPoint
, тогда он не будет соответствовать, поскольку color
не существует).
Это дает понимание того, как можно реализовать равенство иерархии классов case.
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
В конечном итоге можно удовлетворить требования равенства равенства даже для преемника класса case (без переопределения равенства).
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) = ???