Derived operator
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 write | The compiler uses | Ann 7, Bob 9, Cy 7 |
|---|---|---|
a != b | !(a == b) | ann != bob is true |
a > b | b < a | bob > ann is true |
a <= b | a < b || a == b | ann <= cy is true |
a >= b | b < a || a == b | ann >= bob is false |
flowchart LR
eq["== (declared)"] --> ne["!="]
lt["< (declared)"] --> gt[">"]
lt --> le["<="]
eq --> le
lt --> ge[">="]
eq --> geNotice 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.
// 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
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.== 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.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
- Add
let dee = Player { name: "Dee", score: 9 };. Predict all six comparisons between Bob and Dee, then print them. - Which of the six already work on
Moneyfrom the Operator overload lesson? Try each one. - Change the ranking so that a lower score is better, as in golf. How many functions did you have to change?
Learn more
- Operator overload — declaring
==and<in the first place - Comparable — ordering through a method that answers with an
Ordering - Comparison — the six operators on numbers
- Comparison operators in the Rux Reference