How do comparison and ordering operators work in EK9?

← Operators and Expressions · Ref: Q239

EK9 provides a comprehensive set of comparison and ordering operators. All comparison operators MUST be pure, take exactly 1 argument, and return specific types. If either operand is unset, the result is unset.

EQUALITY AND INEQUALITY

The == operator tests equality and returns Boolean. The <> operator tests inequality and returns Boolean. Both take one argument of the same type.

  operator == as pure
    -> other as MyType
    <- rtn as Boolean: ...

ORDERING

The <, >, <=, >= operators test ordering and return Boolean. They take one argument.

  if score1 > score2
    stdout.println("Higher")

THREE-WAY COMPARISON (SPACESHIP)

The <=> operator returns Integer: negative if less, zero if equal, positive if greater. This is the foundation for sorting.

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

When you implement <=>, you typically derive <, >, <=, >= from it.

FUZZY COMPARISON

The <~> operator returns Integer representing a distance or similarity score. For String, it returns the Levenshtein edit distance. The lower the value, the more similar.

  distance <- "hello" <~> "hallo"
  stdout.println(`Edit distance: ${distance}`)

CONTAINS AND MATCHES

The 'contains' operator checks if one value contains another. The 'matches' operator checks pattern matching (e.g., regex). Both are pure, take 1 argument, and return Boolean.

  if greeting contains "hello"
    stdout.println("Found")
  if email matches /[a-z]+@[a-z]+\.[a-z]+/
    stdout.println("Valid")

UNSET PROPAGATION

If either operand is unset, comparison results are unset (not false). This prevents silent logic errors with missing data.

See Q96 for class operators. See Q238 for the complete operator set. See Q243 for coalescing operators that handle unset values.

Example

defines module qa.operators.comparison

  defines class

    Temperature
      celsius <- Float()

      Temperature() as pure
        -> celsius as Float
        this.celsius :=: celsius

      operator == as pure
        -> other as Temperature
        <- rtn as Boolean: celsius == other.celsius

      operator <> as pure
        -> other as Temperature
        <- rtn as Boolean: celsius <> other.celsius

      operator <=> as pure
        -> other as Temperature
        <- rtn as Integer: celsius <=> other.celsius

      operator < as pure
        -> other as Temperature
        <- rtn as Boolean: celsius < other.celsius

      operator > as pure
        -> other as Temperature
        <- rtn as Boolean: celsius > other.celsius

      operator <= as pure
        -> other as Temperature
        <- rtn as Boolean: celsius <= other.celsius

      operator >= as pure
        -> other as Temperature
        <- rtn as Boolean: celsius >= other.celsius

      operator $ as pure
        <- rtn as String: `${celsius}C`

      override operator ? as pure
        <- rtn as Boolean: celsius?

  defines program

    ComparisonDemo()
      stdout <- Stdout()

      freezing <- Temperature(0.0)
      boiling <- Temperature(100.0)
      body <- Temperature(37.0)

      // === EQUALITY AND INEQUALITY ===

      stdout.println(`Equal: ${freezing == boiling}`)
      stdout.println(`Not equal: ${freezing <> boiling}`)

      // === ORDERING ===

      stdout.println(`Freezing < boiling: ${freezing < boiling}`)
      stdout.println(`Body >= freezing: ${body >= freezing}`)

      // === THREE-WAY COMPARISON ===

      cmp <- freezing <=> boiling
      stdout.println(`Compare: ${cmp}`)

      // === FUZZY COMPARISON ON STRINGS ===

      reference <- "hello"
      distance <- reference <~> "hallo"
      stdout.println(`Fuzzy distance: ${distance}`)

      // === CONTAINS AND MATCHES ===

      greeting <- "hello world"
      stdout.println(`Contains hello: ${greeting contains "hello"}`)

Common mistakes

E07500 — The spaceship comparison operator must be marked 'as pure' because comparisons cannot have side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details.

Incorrect:

operator <=>

Correct:

operator <=> as pure

E07550 — The <=> operator must return Integer for three-way comparison ordering. Returning String instead triggers E07550. See ek9 -h E07550 for details.

Incorrect:

<- rtn as String: celsius <=> other.celsius

Correct:

<- rtn as Integer: celsius <=> other.celsius

E07520 — The < operator must return Boolean (true or false). Returning Integer instead triggers E07520. See ek9 -h E07520 for details.

Incorrect:

<- rtn as Integer: celsius < other.celsius

Correct:

<- rtn as Boolean: celsius < other.celsius
Other ways to ask this
  • How do I compare two objects in EK9?
  • What is the spaceship operator in EK9?
  • How does fuzzy matching work with the <~> operator?

Coming from another language?

Java: equals() for equality, compareTo() for ordering (returns int), no fuzzy match built-in. Python: __eq__, __lt__, __gt__ etc., functools.total_ordering derives from __lt__ and __eq__. Rust: PartialEq, Eq, PartialOrd, Ord traits, derived with #[derive()]. Go: no operator overloading, manual comparison functions. Kotlin: compareTo() operator, == delegates to equals(). JavaScript: no custom comparisons, == with type coercion problems. EK9: ==, <>, <, >, <=, >= return Boolean, <=> returns Integer, <~> returns fuzzy distance, all must be pure with 1 argument.

Keywords: fuzzy, matches, Integer, operator, sort, expression, greater, Boolean, equal, contains, spaceship, unset, less, comparison, ordering