Why does 'sort by' with my comparator function report E07188?

← Streams and Pipelines · Ref: Q2283

A comparator is an ordering, exactly as a '<=>' is, so it must put every two SET values in order. 'sort by', a sorted queue and anything else that orders by it depend on that: a comparator that answers UNSET for two set values leaves them with no order, and the result would depend on which way the list arrived.

So E07188 is reported where something orders by a function whose Integer result can be unset for two set values: 'sort by byAmount', a function given where a Comparator is expected ('queue.withComparator(byAmount)', 'ordering as Comparator of Money: byAmount'), and a dynamic function declared 'is Comparator of T'. The sources that make it partial are the ones a '<=>' has (Q2262): an unset 'Integer()', or the '<=>' of Money (defined only within one currency) or Dimension (only within one unit suffix), directly or through a helper.

A function is not reported for its shape alone. One that takes two values of a type and returns an Integer that can be unset - a 'safeDivide' that is unset for a zero divisor - is ordinary until something orders by it.

What the compiler cannot see is still never treated as equal: a comparator by a key that can be unset (a currency with no exchange rate) stops 'sort by' or a PriorityQueue at run time with an EK9 Exception naming the two values and the comparator ('Sort cannot order 1.00#GBP and 2.00#USD: 'byWeight' gives no answer between them').

The fix is the one a '<=>' gets: compare the part that groups the values first, then the part that orders inside a group. For Money that is '#>' (its currency, a String) and then '#<' (its amount, a Float); for a Dimension the same two operators give the unit suffix and the value.

Example

#!ek9
defines module qa.streams.comparator.must.order.every.pair

  defines function

    //Every pair of prices is ordered: by currency (a String) first, then by amount (a Float)
    byCurrencyThenAmount() as pure
      ->
        first as Money
        second as Money
      <- rtn as Integer: #> first <=> #> second
      if rtn == 0
        rtn: #< first <=> #< second

  defines program

    ComparatorDemo()
      stdout <- Stdout()
      prices <- [10#GBP, 5#USD, 3#GBP]

      sorted <- cat prices | sort by byCurrencyThenAmount | collect as List of Money
      stdout.println(`sorted: ${sorted}`)

      ordering as Comparator of Money: byCurrencyThenAmount
      stdout.println(`10 GBP before 5 USD: ${ordering(10#GBP, 5#USD) < 0}`)
      stdout.println(`3 GBP before 10 GBP: ${ordering(3#GBP, 10#GBP) < 0}`)

Common mistakes

E07188 — Money orders only within one currency, so a comparator that returns Money's '<=>' gives 10#GBP and 5#USD no order, and 'sort by' could not place them. Compare the currency first, then the amount, so every pair is ordered. See ek9 -h E07188.

Incorrect:

      <- rtn as Integer: first <=> second

Correct:

      <- rtn as Integer: #> first <=> #> second
      if rtn == 0
        rtn: #< first <=> #< second
Other ways to ask this
  • My comparator compares Money values and 'sort by' rejects it - why?
  • How do I write a comparator that sorts prices in different currencies?
  • Why is giving my function to withComparator or a 'Comparator of Money' variable an error?
  • Is a comparator function checked like a '<=>'?

Coming from another language?

Java: a Comparator<BigDecimal> over amounts in different currencies, or one that returns 0 for values it cannot order, compiles and quietly mis-sorts - or throws 'Comparison method violates its general contract!' - only for some inputs. EK9 checks a comparator where something orders by it, as it checks a '<=>' where it is declared.

Keywords: Dimension, E07188, Money, comparator, currency, sort by, total order, unset, withComparator