Why were two of my subclass objects equal when only the field the subclass added was different (E07238)?
← Operators and Expressions · Ref: Q2248
Because the sameness they used was the super class's, written for the super class's fields. A subclass that adds a field and declares no '<=>' (or '==') of its own would inherit the super's, and its '#?' with it: both look only at the super's fields, so Named(1, "ada") and Named(1, "bob") were '==', '<=>' gave 0 and their hashes were equal. Nothing at run time can catch it - both values are Named, so the check that values of different run-time types are never the same does not apply.
So a type that adds fields, concrete or abstract, declares its own sameness wherever it would inherit one, as it already declares its own '?', '$', '$$' and ':=:'. Declaring none is E07238, and the message names every operator the type would inherit, in one error.
The fix is one line:
- 'default operator <=>' below an ordered super (one with '<=>') - it compares the super's '<=>' first, then the new fields, and generates '#?' too;
- 'default operator ==' below an equatable-only super (one with '==' and no '<=>');
- or the bare 'default operator', which declares all of them and gives the same kind of sameness as the super;
- or a hand-written '<=>' with a hand-written 'override operator #?' over the same fields.
A subclass that adds NO fields needs nothing: the inherited sameness already covers everything it holds. And a sameness WRITTEN BY HAND on an abstract base or a trait is hierarchy-wide sameness (every Shape by area), so its subtypes add fields and declare none (E07182). An abstract type that adds fields therefore chooses: 'default operator' gives it a per-type sameness (each concrete subtype that adds fields declares its own again), while a '<=>' written by hand on it is hierarchy-wide sameness, sealed for every subtype.
Example
#!ek9 defines module qa.operators.subtype.own.sameness defines constant firstName <- "ada" secondName <- "bob" defines class //An ordered type: the bare 'default operator' gives it '<=>' over id, and '#?'. Account as open id as Integer: 1 Account() -> start as Integer this.id: start default operator //Adds a field, so it declares its own sameness: Account's '<=>' first, then name. Named extends Account name as String: String() Named() -> start as Integer label as String super(start) this.name: label default operator ? default operator $ default operator $$ default operator :=: default operator <=> //Adds no field: Account's sameness already covers everything it holds. Plain extends Account Plain() -> start as Integer super(start) defines function flag() -> answer as Boolean <- rtn as String: answer? <- $answer else "unset" defines program SubtypeOwnSameness() stdout <- Stdout() ada <- Named(1, firstName) bob <- Named(1, secondName) stdout.println(`Named(1, ada) == Named(1, bob): ${flag(ada == bob)}`) stdout.println(`Named(1, ada) <=> Named(1, bob): ${ada <=> bob}`) stdout.println(`same hash: ${flag((#? ada) == (#? bob))}`) viaAccount as Account: bob stdout.println(`through an Account reference: ${flag(viaAccount == ada)}`) stdout.println(`Plain(1) == Plain(1): ${flag(Plain(1) == Plain(1))}`)
Common mistakes
E07238 — Named adds 'name' but declares no sameness, so it would inherit Account's '<=>' and '#?', which compare and hash only the id: Named(1, "ada") and Named(1, "bob") would be the same. Declare 'default operator <=>' (it compares Account's first, then name, and gives '#?' too) or the bare 'default operator'. See ek9 -h E07238.
Incorrect:
default operator :=:
Correct:
default operator :=: default operator <=>
Other ways to ask this
- Why does E07238 say my subclass would inherit '<=>' and '#?'?
- Why must a subclass that adds a field declare its own '<=>' or '=='?
- Do I need 'default operator <=>' on a subclass when its super class already has one?
- Why do two objects of my subclass with different names have the same hash code?
Coming from another language?
Java: a subclass that adds a field and inherits equals, hashCode and compareTo silently compares only the super's fields (Effective Java, 3rd ed., Item 10: there is no way to extend an instantiable class and add a value component while preserving the equals contract). Kotlin data classes cannot be extended at all. EK9: the subclass must declare its own sameness, and 'default operator <=>' makes that one line that compares the super's fields first.
Keywords: #?, <=>, ==, E07238, adds fields, default operator, inheritance, sameness, subclass