How do I implement comparison operators on a record for sorting?

← Operators and Expressions · Ref: Q1049

Implement the <=> (spaceship) operator on your record - its sameness - with a matching #? that hashes the same fields. The compiler then GENERATES <, >, <=, >=, == and <> from <=>; you never write them (writing or requesting one is E07181), so they can never disagree with <=>. All comparison operators are pure.

  defines record
    Priority
      level as Integer: 0
      operator <=> as pure
        -> other as Priority
        <- rtn as Integer: level <=> other.level
      operator #? as pure
        <- rtn as Integer: #?level
      default operator

A hand-written <=> needs a hand-written #? (E07184); 'default operator' then supplies the rest ($, ?).

Once <=> is defined, you can sort lists and use coalescing operators:

  smaller <- priorityA <? priorityB
  larger <- priorityA >? priorityB

The coalescing operators <? and >? return the lesser or greater value. They work because the record has <=>. For a different ordering (say by label), give sort a comparator function instead of writing a second <=>.

See Q1050 for coalescing operators on a class. See Q239 for comparison ordering. See Q1051 for operators on traits.

Example

defines module qa.operators.comparisonrecord

  defines record

    Priority
      level as Integer: 0
      label as String: String()

      Priority()
        ->
          level as Integer
          label as String
        this.level :=: level
        this.label :=: label

      operator <=> as pure
        -> other as Priority
        <- rtn as Integer: level <=> other.level

      //A hand-written <=> needs a hand-written #? hashing the same field
      operator #? as pure
        <- rtn as Integer: #?level

      //<, >, <=, >=, == and <> are generated from <=>; this adds $ and ?
      default operator

  defines program

    ComparisonRecordDemo()
      stdout <- Stdout()

      high <- Priority(10, "Critical")
      medium <- Priority(5, "Normal")
      low <- Priority(1, "Low")

      stdout.println(`High > Low: ${high > low}`)
      stdout.println(`Medium <= High: ${medium <= high}`)

      //Coalescing: get the lesser/greater
      lesser <- high <? medium
      greater <- low >? medium
      stdout.println(`Lesser: ${lesser}`)
      stdout.println(`Greater: ${greater}`)

Common mistakes

E07181 — The < operator on a record with <=> is generated from <=> - writing it by hand is E07181 (COMPARISON_GENERATED_FROM_SAMENESS). Delete it: <, >, <=, >=, == and <> all come from <=>. See ek9 -h E07181 for details.

Incorrect:

      operator < as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) < 0

      default operator

Correct:

      default operator

E07184 — A hand-written <=> needs a hand-written #? that hashes what <=> compares (here, level). A defaulted #? would hash every field and disagree with the sameness, so it is E07184. See ek9 -h E07184 for details.

Incorrect:

      default operator #?

Correct:

      operator #? as pure
        <- rtn as Integer: #?level
Other ways to ask this
  • How do <, >, <=, >= work on a record?
  • Show me comparison operators on a custom record
  • How do I make a record sortable in EK9?

Coming from another language?

Java: implements Comparable. Python: __lt__, __gt__, etc. (functools.total_ordering). Rust: derive Ord. EK9: define <=> (and #?); the compiler generates <, >, <=, >=, == and <> from it.

Keywords: sort, record, spaceship, E07184, operator, comparison, sameness, E07181