Why are two shapes not equal when my abstract base has 'default operator' and they have the same name?
← Operators and Expressions · Ref: Q2242
A DEFAULTED sameness on an abstract base belongs to each concrete type, not to the hierarchy. 'default operator' on Shape compares only Shape's own fields, so each concrete subtype that says 'default operator' gets its own '<=>' over ALL its fields - the inherited ones first, then its own - and '==' is generated from it. Values of different run-time types are never the same: a Circle and a Square are not '==' and their '<=>' is unset, even when every field they share is equal.
Only a sameness WRITTEN BY HAND on an abstract base or a trait is hierarchy-wide sameness: an ordering over what every subtype provides ('area() <=> other.area()') compares a Circle with a Square, and no subtype may change it (E07182). Choose that when values of different kinds must be compared; default the sameness when each kind is compared only with its own kind.
Example
#!ek9 defines module qa.operators.defaulted.sameness.abstract defines constant roundName <- "round" defines class //Defaulted on an abstract base: it compares only 'name', and each concrete type has its own. Shape as abstract name as String: String() Shape() -> label as String this.name: label default operator Circle extends Shape radius as Integer: Integer() Circle() -> label as String size as Integer super(label) this.radius: size default operator Square extends Shape side as Integer: Integer() Square() -> label as String edge as Integer super(label) this.side: edge default operator defines function flag() -> answer as Boolean <- rtn as String: answer? <- $answer else "unset" shown() -> comparison as Integer <- rtn as String: comparison? <- $comparison else "unset" defines program DefaultedSamenessOnAbstractBase() stdout <- Stdout() small <- Circle(roundName, 1) sameSmall <- Circle(roundName, 1) larger <- Circle(roundName, 2) square <- Square(roundName, 1) stdout.println(`Circle(round, 1) == Circle(round, 1): ${flag(small == sameSmall)}`) stdout.println(`Circle(round, 1) == Circle(round, 2): ${flag(small == larger)}`) stdout.println(`Circle(round, 1) <=> Circle(round, 2): ${shown(small <=> larger)}`) stdout.println(`Circle(round, 1) == Square(round, 1): ${flag(small == square)}`) stdout.println(`Circle(round, 1) <=> Square(round, 1): ${shown(small <=> square)}`)
Other ways to ask this
- Does a 'default operator' on an abstract class compare every subtype the same way?
- Why is a Circle never equal to a Square when both inherit a defaulted '<=>'?
- Is a defaulted sameness on an abstract base shared by the whole hierarchy?
- Why does a subtype that adds a field compare that field too when the base was defaulted?
Coming from another language?
Java: an equals generated on an abstract class (or by Lombok's @EqualsAndHashCode) and inherited by subclasses compares only the superclass fields, unless every subclass regenerates it with callSuper - and a Circle can then equal a Square that happens to share the fields. Kotlin: data classes cannot be extended. EK9: a defaulted sameness is per-type, includes every field, and a different run-time type is never the same.
Keywords: <=>, ==, abstract base, default operator, equality, hierarchy, run-time type, sameness, subtype