How do I use the Comparator function type for ordering?

← Generics · Ref: Q714

Comparator of T is a pure function type that takes two T values 't1' and 't2' and returns 'r' as Integer.

COMPARATOR SIGNATURE

Comparator of T takes two same-type parameters and returns their ordering:

  cmp <- () is Comparator of String as pure function
    r: t1 <=> t2

Returns negative if t1 < t2, zero if equal, positive if t1 > t2.

ALWAYS PURE

Comparator is always pure — there is no impure variant. Ordering must be deterministic.

CUSTOM ORDERING

Reverse ordering by swapping t1 and t2:

  reverseCmp <- () is Comparator of Integer as pure function
    r: t2 <=> t1

Compare by a derived property:

  lengthCmp <- () is Comparator of String as pure function
    r: length t1 <=> length t2

THE <=> OPERATOR

EK9's comparison operator returns Integer: negative, zero, or positive.

See Q54 for pure function concepts. See Q89 for stream sort operations.

Example

defines module qa.genericsdeep.comparator

  defines function

    <?-
      Helper that finds the smaller of two values using a Comparator.
    -?>
    minimum() as pure
      ->
        first as String
        second as String
        cmp as Comparator of String
      <- result as String: first
      if cmp(first, second) > 0
        result: second

  defines program

    ComparatorDemo()
      stdout <- Stdout()

      fruitA <- "apple"
      fruitB <- "banana"

      //Natural String ordering — uses 't1' and 't2' from Comparator signature
      natural <- () is Comparator of String as pure function
        r: t1 <=> t2

      stdout.println(`'${fruitA}' vs '${fruitB}': ${natural(fruitA, fruitB)}`)

      //Reverse ordering — swap t1 and t2
      reverse <- () is Comparator of String as pure function
        r: t2 <=> t1

      stdout.println(`'${fruitA}' vs '${fruitB}' reversed: ${reverse(fruitA, fruitB)}`)

      //Compare by string length
      byLength <- () is Comparator of String as pure function
        r: length t1 <=> length t2

      stdout.println(`'hi' vs 'hello' by length: ${byLength("hi", "hello")}`)

      //Minimum via Comparator delegate
      smaller <- minimum("zebra", fruitA, natural)
      stdout.println(`Minimum: ${smaller}`)

      //Integer Comparator
      intCmp <- () is Comparator of Integer as pure function
        r: t1 <=> t2

      stdout.println(`42 vs 99: ${intCmp(42, 99)}`)

Common mistakes

E05150 — Comparator is always pure — dynamic functions extending Comparator must use 'as pure function', not just 'as function'. There is no impure variant of Comparator. See ek9 -h E05150 for details.

Incorrect:

() is Comparator of String as function

Correct:

() is Comparator of String as pure function
Other ways to ask this
  • How do I create a Comparator of String for custom sorting?
  • What is the Comparator function type signature in EK9?
  • How do I implement a custom comparison with Comparator?

Coming from another language?

Java: java.util.Comparator<T> — not a @FunctionalInterface by design (has default methods), supports reversed(), thenComparing() composition, can have side effects. Kotlin: Comparator<T> from Java stdlib, or compareBy/compareByDescending helpers. Rust: Fn(&T, &T) -> Ordering — structural, uses Ordering enum not Integer. Go: func(T, T) int — by convention, or sort.Interface for types. C#: Comparer<T> or Comparison<T> delegate. EK9: Comparator of T is always pure — deterministic ordering guaranteed by compiler, takes (T, T) returns Integer, no impure variant exists.

Keywords: comparison, ordering, sort, comparator, delegate, pure, function-type, integer, generic, spaceship