何か他のものを探しているときに、単なる偶然から、ケースクラスの継承がいかに悪魔的であるかについてのコメントをいくつか見つけました。、惨めな人と王様、エルフと魔法使いと呼ばれるものがありProductN、ケースクラスの継承により、ある種の非常に望ましいプロパティがどのように失われるかがありました。では、ケース クラスの継承の何が問題になっているのでしょうか。
2 に答える
一言:平等
caseequalsクラスには、およびの提供された実装が付属していhashCodeます。として知られる同値関係equalsは、次のように機能します (つまり、次のプロパティが必要です)。
- すべてのために
x;x equals xですtrue(再帰) x、y、z; _ ifx equals yandy equals zthenx equals z(他動詞)- のため
xに、y; ifx equals ytheny 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特に、期待どおりに機能しなかったという事実と、パターンマッチャーとの相互作用。
それは全体的に真実ではありません。そして、これは嘘よりも悪いです。
aepurnietが述べたように、定義領域を制限するクラスの後継者は、パターン マッチングが等価として正確に機能する必要があるため、等価を再定義する必要があります (一致しようとすると、存在しないため一致しPointません)。ColoredPointcolor
これにより、ケース クラス階層の等価性をどのように実装できるかが理解できます。
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 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) = ???