E07182: sameness ('<=>' or '==') written by hand on an abstract base or a trait is hierarchy-wide sameness; a subtype inherits it and cannot redefine it

Category: E07xxx Full resolution · Reported in phase: FULL_RESOLUTION · Classification: SAMENESS_ALREADY_DECLARED · All error codes

What went wrong

This type inherits its sameness ('<=>' or '==') from an ABSTRACT base or a TRAIT where it is WRITTEN BY HAND, and declares sameness of its own. A hand-written sameness on an abstract base or a trait is ONE sameness for the whole hierarchy - it is how values of different kinds compare (every Shape ordered by area) - so it is sealed there: every subtype inherits it and cannot change it. That is HIERARCHY-WIDE sameness. Everything else is PER-TYPE sameness, including a DEFAULTED sameness on an abstract base: it compares only the base's own fields and is not sealed, so each subtype may 'default' its own over all its fields, exactly as below any other type.

Why: any subtype refinement of a hierarchy-wide ordering is non-transitive once values of different subtypes are mixed. With Circle ordered by radius and every Shape by area you can get c1 < s, s < c2, yet not c1 < c2 - so a sort depends on the order of its input. Bloch (Effective Java item 10): "there is no way to extend an instantiable class and add a value component while preserving the equals contract". EK9 makes that a rule.

PER-TYPE sameness is that type's own. A subtype of a type with per-type sameness may declare or 'default' its own '<=>' or '==' over all its fields, inherited ones included - and one that adds fields must (E07238) - and values of different run-time types are never the same - '==' is false and '<=>' unset. The compiler checks the run-time type before the comparison runs, so the author only ever writes the same-type comparison.

One sameness per type. A hierarchy-wide '<=>' is the only view the whole hierarchy has: a Card ordered by 'rank()' in its trait has Card(5, "hearts") == Card(5, "spades"), because rank is all the trait compares. To tell suits apart put the suit into the trait's ordering. For other views - by value alone, 'aces high' - write a comparator function and 'sort by' it; a second '<=>' on the type would be a second sameness (see ek9 -q 2280).

EK9 codifies Java's equals/hashCode/compareTo contract (Effective Java items 10, 11 and 14) as a compile error: a type defines its SAMENESS once - '<=>' if it is ordered, '==' if it is equatable only - and every other comparison is generated from it, so two ways of saying "equal" cannot disagree.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1402 (How do I define equality and ordering for my own type?)

How to fix it

  1. Delete the subtype's '<=>' / '==' (and its '#?'): it inherits the hierarchy's.
  2. A bare 'default operator' on the subtype is fine - it gives only the set-check '?', the String and JSON forms '$' and '$$', and the copy.
  3. If each type should compare only with its own kind (per-type sameness), let the abstract base 'default' its sameness (or declare none, where it adds no fields below another sameness - E07238) and give each subtype its own ('default operator' or a hand-written '<=>').
  4. If code needs a different order for one purpose, write a comparator function and 'sort by' it.

Common causes

  • 'default operator <=>' on a subclass of an abstract base that orders by '<=>'
  • A subclass of a trait with '<=>' narrowing it to '<=>(Sub)'

See also

  • E07181: comparisons generated from the sameness
  • E07183: sameness must have a body where it is declared
  • E07184: '#?' goes with the sameness

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E07182: sameness ('<=>' or '==') written by hand on an abstract base or a trait is hierarchy-wide sameness; a subtype inherits it and cannot redefine it

#!ek9
defines module error.e07182.bad

  defines class

    Shape as abstract
      area() as pure abstract
        <- rtn as Float?

      operator <=> as pure
        -> other as Shape
        <- rtn as Integer: area() <=> other.area()

      operator #? as pure
        <- rtn as Integer: #?area()

    Circle extends Shape
      radius as Float: 0.0

      Circle()
        -> radius as Float
        this.radius: radius

      override area() as pure
        <- rtn as Float: 3.14 * radius * radius

      default operator <=>

      default operator ?

      //Shape orders EVERY shape, of any kind, by area: one ordering for the whole hierarchy

Fixed: Inherit it

#!ek9
defines module error.e07182.good

  defines class

    Shape as abstract
      area() as pure abstract
        <- rtn as Float?

      operator <=> as pure
        -> other as Shape
        <- rtn as Integer: area() <=> other.area()

      operator #? as pure
        <- rtn as Integer: #?area()

    Circle extends Shape
      radius as Float: 0.0

      Circle()
        -> radius as Float
        this.radius: radius

      override area() as pure
        <- rtn as Float: 3.14 * radius * radius

      //Inherits '<=>', the comparisons and '#?' from Shape: a Circle orders against any Shape by area
      default operator ?

Questions and answers

These Q&A entries show E07182 and its fix: