Mentre cercavo qualcos'altro, per pura coincidenza mi sono imbattuto in pochi commenti su quanto diabolica sia l'ereditarietà della case case. C'era questa cosa chiamata ProductN
, disgrazia e re, elfi e maghi e come si perde una specie di proprietà molto desiderabile con l'ereditarietà delle classi di casi. Quindi cosa c'è di così sbagliato nell'ereditarietà della case case?Che cosa è * così * sbagliato nell'ereditarietà della case case?
risposta
Una sola parola: uguaglianza
case
classi sono dotati di un'implementazione fornito di equals
e hashCode
. La relazione di equivalenza, noto come equals
così (cioè deve avere le seguenti proprietà):
- Per tutti
x
;x equals x
ètrue
(riflessivo) - per
x
,y
,z
; sex equals y
ey equals z
quindix equals z
(transitiva) - per
x
, ; se poix equals y
y equals x
(simmetrica)
Non appena si consente per l'uguaglianza all'interno di una gerarchia di ereditarietà si può rompere 2 e 3. Questo è banalmente dimostrato dal seguente esempio:
case class Point(x: Int, y: Int)
case class ColoredPoint(x: Int, y: Int, c: Color) extends Point(x, y)
Poi abbiamo :
Point(0, 0) equals ColoredPoint(0, 0, RED)
Ma non
ColoredPoint(0, 0, RED) equals Point(0, 0)
Si potrebbe sostenere che tutte le gerarchie di classi possono avere questo problema, e questo è vero. Ma le classi di casi esistono specificamente per semplificare l'uguaglianza dal punto di vista dello sviluppatore (tra le altre ragioni), quindi avere loro il comportamento non intuitivamente sarebbe la definizione di un proprio obiettivo!
Ci sono stati anche altri motivi; in particolare il fatto che copy
did not work as expected e interaction with the pattern matcher.
Questo non è nel complesso vero. E questo è peggio della menzogna.
Come menzionato da aepurniet in qualsiasi classe successore caso che restringe un'area definizione deve ridefinire l'uguaglianza, poiché pattern matching deve funzionare esattamente come uguaglianza (se cercare di abbinare Point
come ColoredPoint
allora non sarà abbinato poiché color
non è disponibile).
Questo consente di comprendere come implementare l'uguaglianza della gerarchia delle classi di casi.
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 è possibile soddisfare esigenze del rapporto uguaglianza anche per il caso di classe successore (senza sovrascrivere della parità).
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) = ???
E che dire di una piccola elaborazione :)? –
Sembra che un'equivalenza asimmetrica sarebbe una cosa utile nel paradigma OO, nello stesso modo in cui a livello di tipo un 'ColoredPoint' è-a 'Punto' ma non viceversa. Potrebbe chiamarlo qualcosa di diverso da "uguali" anche se ... forse "subequidi"? –
@LuigiPlinge forse 'canReplace',' sostituisce', 'specifica' o' sovrascrive 'per la relazione inversa? Qualsiasi cosa indichi '> =' -ness (o '>:' se ti piace) di esso. Mi sembra molto più facile nominarlo in termini di '> =' piuttosto che '<='. –