Can a trait define comparison and hashcode operators in EK9?
← Operators and Expressions · Ref: Q1051
Yes. A trait can declare the SAMENESS of every type that implements it: a concrete <=> (or ==) plus a matching #?. Sameness must be concrete where it is declared - a bodiless (abstract) <=> in a trait is E07183 - so the trait writes the body over its own abstract accessors, and implementers supply those accessors, not an override of <=>.
defines trait Rankable rank() as pure <- rtn as Integer?
operator <=> as pure -> other as Rankable <- rtn as Integer: rank() <=> other.rank()
operator #? as pure <- rtn as Integer: #?rank()
Any class 'with trait of Rankable' implements rank() and inherits the sameness. The compiler generates <, >, <=, >=, == and <> from the trait's <=> (writing them is E07181), and the sameness is sealed: an implementer may not redeclare <=>, == or #? (E07182/E07184). The trait contract guarantees that all Rankable objects can be compared, sorted and used with <? and >?.
This pattern is useful for:
- Defining sortable contracts
- Ensuring all types in a collection support comparison
- Building generic algorithms that depend on ordering
A different ordering for one use (say by name) is a comparator function given to sort, never a second <=>.
Note: override is required when implementing the trait's accessor in a class (override rank()).
See Q1049 for comparison operators on records. See Q1050 for coalescing on classes. See Q210 for trait delegation.
Example
defines module qa.operators.operatorsontrait defines trait Rankable rank() as pure <- rtn as Integer? //The sameness, written once over the trait's own abstract accessor. //<, >, <=, >=, == and <> with any Rankable are generated from it. operator <=> as pure -> other as Rankable <- rtn as Integer: rank() <=> other.rank() operator #? as pure <- rtn as Integer: #?rank() defines class Task with trait of Rankable priority as Integer: 0 taskName as String: String() Task() -> taskName as String priority as Integer this.taskName :=: taskName this.priority :=: priority override rank() as pure <- rtn as Integer: priority override operator ? as pure <- rtn as Boolean: taskName? and priority? operator $ as pure <- rtn as String: `${taskName}(${priority})` defines program TraitOperatorsDemo() stdout <- Stdout() taskA <- Task("Fix bug", 3) taskB <- Task("Deploy", 8) stdout.println(`A < B: ${taskA < taskB}`) stdout.println(`A > B: ${taskA > taskB}`) stdout.println(`A hash: ${#? taskA}`) //Coalescing: get higher priority urgent <- taskA >? taskB stdout.println(`More urgent: ${urgent}`)
Common mistakes
E05120 — Overriding an inherited trait member (here rank() from trait Rankable) requires the 'override' keyword, otherwise the compiler reports E05120. See ek9 -h E05120 for details.
Incorrect:
rank() as pure
Correct:
override rank() as pure
E07183 — Sameness must be concrete where it is declared: a bodiless (abstract) <=> in a trait is E07183 (SAMENESS_MUST_BE_CONCRETE). Write the body over the trait's own abstract accessor (rank()); implementers supply rank(), not an override of <=>. See ek9 -h E07183 for details.
Incorrect:
operator <=> as pure -> other as Rankable <- rtn as Integer?
Correct:
operator <=> as pure -> other as Rankable <- rtn as Integer: rank() <=> other.rank()
Other ways to ask this
- Show me operator definitions inside a trait
- Do traits support #? and comparison operators?
- Define a sortable contract using a trait with operators
Coming from another language?
Java: Comparable interface. Rust: Ord trait. Python: __lt__ protocol. EK9: a trait declares a concrete <=> and #? over its abstract accessors; implementers supply the accessors.
Keywords: E07183, contract, abstract, operator, comparison, hashcode, sameness, trait