How are defaulted comparison operators derived - does a defaulted == agree with my <=>?
← Operators and Expressions · Ref: Q1398
Every defaulted comparison is answered by the type's <=> when it has one. A defaulted == is '(a <=> b) == 0', <> is '(a <=> b) <> 0', and <, <=, >, >= compare the <=> result with zero - so a defaulted operator can never disagree with the type's ordering.
WRITE <=>, DEFAULT THE REST
Ticket
id as Integer: 0
holder as String: ""
operator <=> as pure
-> other as Ticket
<- rtn as Integer: id <=> other.id
operator #? as pure
<- rtn as Integer: #?id
default operator //generates ==, <>, <, <=, >, >= and operator ? as well
A hand-written <=> needs a hand-written #? hashing what it compares (E07184).
Tickets are ordered by id alone, so two tickets with the same id are ==, even though their holders differ.
NO ORDERING, NO <=>
Some types have == but no <=> - Dict, List, Optional, Result. A type with such a field cannot be ordered: a bare 'default operator' gives it == (field by field) and <> (its negation), but no <, <=, >, >= or <=> - using one is E07620.
EXPLICIT DEFAULTS
When requesting operators one at a time, request the sameness only: 'default operator <=>' (or 'default operator ==' for a type that cannot be ordered). ==, <>, <, <=, >, >= are always generated from it and are never written or requested - 'default operator <>' is E07181.
A bare 'default operator' generates every operator the type does not write itself, even beside an operator of the same name that takes a different argument (an ==(Integer) does not stop ==(Ticket) being generated).
Example
defines module qa.operators.defaulted.comparisons defines class Ticket id as Integer: 0 holder as String: "" Ticket() -> identity as Integer name as String id: identity holder: name operator <=> as pure -> other as Ticket <- rtn as Integer: id <=> other.id //A hand-written <=> needs a hand-written #? hashing what it compares operator #? as pure <- rtn as Integer: #?id default operator Badge code as String: "" Badge() -> text as String code: text default operator ? default operator <=> defines record Settings values as Dict of (String, Integer): Dict() of (String, Integer) default operator defines program DefaultedComparisons() stdout <- Stdout() first <- Ticket(7, "Steve") second <- Ticket(7, "Alex") stdout.println($(first == second)) stdout.println($(first < Ticket(8, "Sam"))) stdout.println($(Badge("a") <> Badge("b"))) stdout.println($(Settings() == Settings()))
Common mistakes
E07620 — Settings has a Dict field, and a Dict has no <=>, so Settings cannot be ordered: its bare 'default operator' generates == and <> but no ordering operators.
Incorrect:
stdout.println($(Settings() < Settings()))
Correct:
stdout.println($(Settings() == Settings()))
E07181 — <> is always generated from the type's sameness (<=> or ==), so it is never written or requested - 'default operator <>' is E07181. Request (or write) the sameness, <=>, and <> comes with it.
Incorrect:
default operator <>
Correct:
default operator <=>
Other ways to ask this
- If I write <=> myself, what does 'default operator' generate for == and <>?
- Why is == true when two objects differ in a field <=> ignores?
- Why can't I use < on a record that has a Dict field?
- Why is 'default operator <>' rejected when I only want inequality?
Coming from another language?
Java: equals() and compareTo() are written independently and 'consistent with equals' is only a documented recommendation - TreeSet and HashSet can disagree about the same objects. Kotlin: data classes generate equals() but not compareTo(), so the two can drift. Rust: #[derive(PartialEq, PartialOrd)] derives both field by field, but a hand-written Ord beside a derived PartialEq can disagree. EK9: the defaulted comparisons are generated from <=>, so they agree with it by construction.
Keywords: E07181, equals, ==, compareTo, <>, default, operator, derived, orderable, comparison, E07620, consistent, ordering, Dict, equality, <=>