Why can't my subclass declare only '==' when its super class has '<=>' (E07186)?

← Operators and Expressions · Ref: Q2244

Below an ORDERED type - a super class with '<=>' - a subtype that declares its own sameness must declare '<=>', by hand or with 'default operator <=>'; '==' alone is E07186.

With '==' alone the subtype would answer two questions differently: its own '==' compares all its fields, while the '<=>' it inherits compares only the super's. Two Named values with the same count and different names would be different by '==' and the same by '<=>' - so a sort, '<' or '<?' would treat them as one.

With '<=>' declared, 'default operator <=>' compares the super's '<=>' first and then the subtype's own fields, and '==' '<>' '<' '<=' '>' '>=' are generated from it, so they always agree. Below an equatable-only super (one with '==' and no '<=>') '==' alone is right. A bare 'default operator' gives '<=>' below an ordered super only where every field of the subtype can be ordered; a field with no '<=>' (a Dict or a List) makes it E07186 too, naming the field - hold the super in a field instead of extending it, or write '<=>' by hand. Below an equatable-only super it gives '==' (and '#?'). A subtype that adds fields must declare its own sameness one of these ways - declaring none is E07238 (Q2248).

Example

#!ek9
defines module qa.operators.ordered.super.needs.ordering

  defines constant
    firstName <- "ada"
    secondName <- "bob"

  defines class

    //An ordered type: the bare 'default operator' gives it '<=>' over count.
    Holder as open
      count as Integer: 1

      Holder()
        -> start as Integer
        this.count: start

      default operator

    Named extends Holder
      name as String: String()

      Named()
        ->
          start as Integer
          label as String
        super(start)
        this.name: label

      //'<=>' compares Holder's count first, then name; '==' and the rest are generated from it.
      default operator <=>
      default operator ?
      default operator $
      default operator $$
      default operator :=:
      default operator #?

    //Every field of Badge can be ordered, so its bare 'default operator' keeps it ordered below Holder.
    Badge extends Holder
      tier as String: "gold"

      Badge()
        -> start as Integer
        super(start)

      default operator

  defines function

    flag()
      -> answer as Boolean
      <- rtn as String: answer? <- $answer else "unset"

  defines program

    OrderedSuperNeedsOrdering()
      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): ${flag(ada < bob)}`)

      sorted <- List() of Named
      cat [Named(2, firstName), bob, ada] | sort > sorted
      stdout.println(`sorted: ${sorted}`)

      lowBadge <- Badge(1)
      highBadge <- Badge(2)
      stdout.println(`Badge(1) < Badge(2): ${flag(lowBadge < highBadge)}`)

Common mistakes

E07186 — Holder is ordered, so Named's own sameness must be '<=>': the '<=>' Named would otherwise inherit compares only count and would disagree with a '==' over count and name. See ek9 -h E07186.

Incorrect:

      default operator ==

Correct:

      default operator <=>

E07186 — A Dict has no '<=>', so Badge's bare 'default operator' cannot give Badge a '<=>' and Badge would be equatable only while Holder is ordered. Keep every field orderable, write '<=>' by hand, or hold a Holder in a field instead of extending it. See ek9 -h E07186.

Incorrect:

      tier as Dict of (String, String): Dict() of (String, String)

Correct:

      tier as String: "gold"
Other ways to ask this
  • What does E07186 mean?
  • Why must a subtype of an ordered type declare '<=>'?
  • Why is 'default operator ==' rejected on a class that extends a class with 'default operator'?
  • How does a subclass that adds a field keep its ordering consistent with its equality?

Coming from another language?

Java: a subclass that overrides equals but inherits compareTo is 'inconsistent with equals' - the Comparable javadoc warns that a TreeSet or TreeMap then behaves strangely, and nothing stops it. EK9 makes that inconsistency a compile error: a subtype of an ordered type extends the ordering, and equality is generated from it.

Keywords: <=>, ==, E07186, consistent with equals, default operator, ordering, sameness, subclass