Why is a Holder never equal to a Named that extends it, even with the same count?
← Operators and Expressions · Ref: Q2243
Values of different run-time types are never the same in EK9. Per-type sameness - the usual kind - is the type's own: '==' between a Holder and a Named is false, '<>' is true and '<=>' is UNSET - EK9's answer for 'not comparable' - whatever fields they share. The compiler puts the run-time type check in front of the comparison, so the author only writes the same-type case.
The OBJECT decides which comparison runs, never the variable: 'held as Holder: Named(1, "x")' compared with another Named runs Named's '==', over all of Named's fields.
So collections keep different kinds apart: 'contains' does not find a Named by an equal-looking Holder, and a Dict keyed by Holder(2) and Named(2, ...) holds two entries.
To compare values of different kinds deliberately, give their ABSTRACT base or trait a hand-written sameness over what every kind provides (every Shape by area) - that one is hierarchy-wide sameness.
Example
#!ek9 defines module qa.operators.different.types.never.same defines constant firstName <- "x" defines class 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 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 DifferentTypesNeverTheSame() stdout <- Stdout() plain <- Holder(2) named <- Named(2, firstName) stdout.println(`Holder(2) == Named(2, x): ${flag(plain == named)}`) stdout.println(`Holder(2) <> Named(2, x): ${flag(plain <> named)}`) stdout.println(`Holder(2) <=> Named(2, x): ${shown(plain <=> named)}`) //The object decides: held is a Named, so Named's '==' runs. held as Holder: Named(2, firstName) stdout.println(`held == Named(2, x): ${flag(held == named)}`) stdout.println(`held == Named(2, y): ${flag(held == Named(2, "y"))}`) holders as List of Holder: [named] stdout.println(`contains Holder(2): ${flag(holders contains plain)}`) counts as Dict of (Holder, String): {plain: "plain"} newEntry as DictEntry of (Holder, String): DictEntry(held, "named") counts += newEntry stdout.println(`entries: ${length counts}`)
Other ways to ask this
- Why does '<=>' between a class and its subclass give unset?
- Can a value of a subtype be equal to a value of its super type in EK9?
- Why does 'contains' not find a subtype value with the same fields?
- Why does a Dict keep a Holder key and a Named key with the same count apart?
- Which '==' runs when I compare through a super-typed variable?
Coming from another language?
Java: equals(Object) in a class hierarchy has to choose between instanceof (not symmetric once a subclass adds a field) and getClass() == o.getClass() (Effective Java item 10); compareTo has no answer for 'not comparable' and must throw. Kotlin: the same choice in an open class's equals. EK9: the getClass() rule is the language's, generated for you, and '<=>' answers unset instead of throwing.
Keywords: <=>, ==, Dict key, contains, equality, polymorphism, run-time type, sameness, subtype, unset