Interfaces · Lesson 12.9

Derived operator

Source
Declare == and <, and get !=, >, <= and >= derived from them.
You'll need: Operator overload

There are six comparison operators, but a type declares at most two of them. From == the compiler derives !=, and from < together with == it derives the other three. So there is less to write — and, more importantly, the six can never disagree with one another.

Two declared

Players are ranked by score alone, so two players with the same score tie, whatever their names:

extend Player {
    func ==(self: &Player, other: Player) -> bool {
        return self.score == other.score;
    }

    func <(self: &Player, other: Player) -> bool {
        return self.score < other.score;
    }
}

Player could not use field-by-field == from Structural equality at all, because its name is a slice. The declared == is what makes players comparable in the first place.

Four derived

PrintLine("Ann != Bob {}", ann != bob);
PrintLine("Bob >  Ann {}", bob > ann);
PrintLine("Ann <= Cy  {}", ann <= cy);
PrintLine("Ann >= Bob {}", ann >= bob);

None of these four operators is declared on Player. The compiler rewrites each one in terms of the two that are:

You writeThe compiler usesAnn 7, Bob 9, Cy 7
a != b!(a == b)ann != bob is true
a > bb < abob > ann is true
a <= ba < b || a == bann <= cy is true
a >= bb < a || a == bann >= bob is false
flowchart LR
    eq["== (declared)"] --> ne["!="]
    lt["< (declared)"] --> gt[">"]
    lt --> le["<="]
    eq --> le
    lt --> ge[">="]
    eq --> ge

Notice ann <= cy: Ann is not less than Cy, but the two tie at 7, so the == half makes it true.

Why they cannot disagree

If a type wrote all six by hand, a slip in one — >= comparing names while < compares scores — would make a >= b and a < b true at once, and any code that sorts or searches would quietly misbehave. Deriving the four from two leaves nothing to slip.

A type may still declare one of the derived operators itself, and then its own declaration is used instead. That is rarely a good idea: it is exactly how the six come to disagree.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// There are six comparison operators, but a type declares at most two of them. From `==` the
// compiler derives `!=`, and from `<` together with `==` it derives the other three:
//     a != b    means    !(a == b)
//     a >  b    means    b < a
//     a <= b    means    a < b || a == b
//     a >= b    means    b < a || a == b
// So there is less to write, and the six can never disagree with one another.
//
// A type may still declare one of the derived operators itself, and then its own declaration is
// used instead. That is rarely a good idea: it is exactly how the six come to disagree.
import Io::PrintLine;

// Players are ranked by score alone. Two players with the same score tie, whatever their names.
struct Player {
    name: char8[..];
    score: int32;
}

extend Player {
    func ==(self: &Player, other: Player) -> bool {
        return self.score == other.score;
    }

    func <(self: &Player, other: Player) -> bool {
        return self.score < other.score;
    }
}

func Main() -> int {
    let ann = Player { name: "Ann", score: 7 };
    let bob = Player { name: "Bob", score: 9 };
    let cy = Player { name: "Cy", score: 7 };

    // The two that were declared.
    PrintLine("Ann == Cy  {}", ann == cy);
    PrintLine("Ann <  Bob {}", ann < bob);

    // The four that were derived.
    PrintLine("Ann != Bob {}", ann != bob);
    PrintLine("Bob >  Ann {}", bob > ann);
    PrintLine("Ann <= Cy  {}", ann <= cy);
    PrintLine("Ann >= Bob {}", ann >= bob);

    // Derivation needs something to start from. Delete `<` and `bob > ann` is refused:
    // "operator '>' is not defined for 'Player'", with the hint "declare '>' on 'Player', or the
    // '<' it is derived from". And `Player` could not use field-by-field `==` at all, since its
    // `name` is a slice; the declared `==` is what makes players comparable.
    return 0;
}

Run it

cd Examples/Interfaces/DerivedOperator
rux run
Ann == Cy  true
Ann <  Bob true
Ann != Bob true
Bob >  Ann true
Ann <= Cy  true
Ann >= Bob false

Common mistakes

Nothing to derive from.
Delete < and bob > ann is refused with error: operator '>' is not defined for 'Player', together with the hint "declare '>' on 'Player', or the '<' it is derived from". ann < bob fails the same way.
Deleting == from a type that cannot compare its fields.
Without the declared ==, ann == cy falls back to structural equality, which Player cannot have: error: structural equality for 'Player' is unavailable because element type 'char8[..]' has no '==' operator. !=, <= and >= are lost with it.
Declaring a derived operator that disagrees.
A hand-written != replaces the derived one. Declare it to return true always, and ann != cy and ann == cy are both true. Leave !=, >, <= and >= to the compiler.

Try it yourself

  1. Add let dee = Player { name: "Dee", score: 9 };. Predict all six comparisons between Bob and Dee, then print them.
  2. Which of the six already work on Money from the Operator overload lesson? Try each one.
  3. Change the ranking so that a lower score is better, as in golf. How many functions did you have to change?

Learn more