What is the difference between hierarchy-wide sameness and per-type sameness, and which one do I have?
← Operators and Expressions · Ref: Q2282
There are two kinds, and which you have depends on how you write it:
HIERARCHY-WIDE sameness is a '<=>' (or '==') written BY HAND on an abstract base or a trait, over what every kind provides (an 'area()'). It compares any two values in the hierarchy, whatever their run-time types, so Square(2.0) == Strip(4.0, 1.0) when both have area 4. It is sealed: no sub-type may declare its own (E07182), so fields a sub-type adds are not compared. Choose it when different kinds must compare, sort and select together.
PER-TYPE sameness is everything else: a concrete type's own '<=>' or '==', or a DEFAULTED one even on an abstract base ('default operator' on Pet compares only Pet's own fields). Each sub-type that adds fields declares its own over all its fields (E07238), and values of different run-time types are never the same: Dog("rex") == Cat("rex") is false and their '<=>' is unset. Choose it when each kind is compared only with its own kind.
A type has one sameness. Another view of it (by value alone, 'aces high') is a comparator function given to 'sort by' (Q2280).
Example
#!ek9 defines module qa.operators.two.kinds.of.sameness defines constant rexName <- "rex" defines class <?- HIERARCHY-WIDE sameness: '<=>' written by hand on an abstract base, over what every kind provides. Every Shape, of any kind, is ordered by area, and no sub-type may change that. -?> 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() Square extends Shape side as Float: 0.0 Square() -> edge as Float this.side: edge override area() as pure <- rtn as Float: side * side default operator ? Strip extends Shape width as Float: 0.0 height as Float: 0.0 Strip() -> across as Float up as Float this.width: across this.height: up override area() as pure <- rtn as Float: width * height default operator ? <?- PER-TYPE sameness: 'default operator' on an abstract base compares only the base's own fields. Each kind has its own, over all its fields, and kinds are never the same as each other. -?> Pet as abstract name as String: String() Pet() -> petName as String this.name: petName default operator Dog extends Pet breed as String: String() Dog() -> petName as String kind as String super(petName) this.breed: kind default operator Cat extends Pet indoor as Boolean: true Cat() -> petName as String inside as Boolean super(petName) this.indoor: inside default operator defines function flag() -> answer as Boolean <- rtn as String: answer? <- $answer else "unset" defines program TwoKindsOfSameness() stdout <- Stdout() square <- Square(2.0) strip <- Strip(4.0, 1.0) stdout.println(`Square(2) == Strip(4, 1), both area 4: ${flag(square == strip)}`) rex <- Dog(rexName, "collie") tom <- Cat(rexName, true) stdout.println(`Dog rex == Cat rex, same name: ${flag(rex == tom)}`) stdout.println(`Dog rex == Dog rex: ${flag(rex == Dog(rexName, "collie"))}`) stdout.println(`Dog rex == Dog rex, another breed: ${flag(rex == Dog(rexName, "poodle"))}`)
Common mistakes
E07182 — Shape's '<=>' is written by hand on an abstract base: hierarchy-wide sameness, sealed for every sub-type. Square cannot declare or default its own. If each kind should compare only with its own kind, remove the hand-written '<=>' from Shape and default it in each type instead. See ek9 -h E07182.
Incorrect:
<- rtn as Float: side * side default operator ? default operator <=>
Correct:
<- rtn as Float: side * side default operator ?
Other ways to ask this
- When is a Square equal to a Strip of the same area, and a Dog never equal to a Cat?
- How do I decide whether the sameness belongs to the whole hierarchy or to each type?
- Why does 'default operator' on an abstract base not make all its sub-types comparable?
- Is my sameness hierarchy-wide?
Coming from another language?
Java: equals in a class hierarchy has to choose between instanceof (not symmetric once a subclass adds a field) and getClass (a subclass never equals its super), and that choice is made class by class. EK9: a hand-written sameness on an abstract base or trait is the whole hierarchy's, any other sameness is the type's own, and the compiler enforces the difference.
Keywords: <=>, ==, E07182, E07238, abstract base, default operator, hierarchy-wide, per-type, sameness, trait