How do I implement all the operators my custom type needs?
← Operators and Expressions · Ref: Q245
Choose operators based on how your type will be used. Use 'default operator' for field-by-field behaviour, override for custom logic.
MINIMUM USEFUL SET
Most types need: a sameness (<=> or ==) with its #?, $ (string), ? (isSet).
COMPARISON - the sameness, written ONCE (pure, 1 arg)
operator <=> as pure -> other as T <- rtn as Integer (ordered type) operator == as pure -> other as T <- rtn as Boolean (equatable-only type, instead of <=>)
The compiler generates the rest: ==, <>, <, <=, >, >= from <=>; <> from ==. Writing them by hand is E07181. A hand-written sameness needs a hand-written #? hashing the same fields (E07184).
ARITHMETIC (pure, 1 arg, return new value)
operator + as pure -> other as T <- rtn as T operator - as pure -> other as T <- rtn as T
UNARY (pure, 0 args)
operator - as pure <- rtn as T (negate) operator #^ as pure <- rtn as OtherType (promote)
MUTATION (NOT pure, no return)
operator += -> other as T operator :=: -> source as T (copy)
CONVERSION (pure, 0 args, enforced return types)
operator $ as pure <- rtn as String operator #? as pure <- rtn as Integer operator ? as pure <- rtn as Boolean
ERROR SCENARIOS
E07500 - must be pure. E07510 - cannot be pure. E06280/E06290 - wrong param count. E07520/E07550/E07570/E07580 - wrong return type.
See Q96 for class operator syntax. See Q98 for record copy/merge/replace. See Q116 for default operator. See Q238 for complete operator set. See Q239 for comparison. See Q240 for arithmetic. See Q241 for mutation. See Q242 for conversion.
Example
#!ek9 defines module qa.operators.customtype defines class Temperature celsius <- Float() default private Temperature() as pure Temperature() as pure -> celsius as Float this.celsius :=: celsius celsius() as pure <- rtn as Float: Float(celsius) // === COMPARISON: the sameness, written ONCE (pure, 1 arg, returns Integer) === // ==, <>, <, <=, >, >= with another Temperature are generated from it operator <=> as pure -> other as Temperature <- rtn as Integer: celsius <=> other.celsius // === ARITHMETIC (pure, 1 arg, return new value) === operator + as pure -> other as Temperature <- rtn as Temperature: Temperature(celsius + other.celsius) operator - as pure -> other as Temperature <- rtn as Temperature: Temperature(celsius - other.celsius) // === UNARY (pure, 0 args, return value) === operator - as pure <- rtn as Temperature: Temperature(-celsius) operator #^ as pure <- rtn as Float: Float(celsius) // === CONVERSION (pure, 0 args, strictly typed returns) === operator $ as pure <- rtn as String: `${celsius}C` operator #? as pure <- rtn as Integer: #?celsius override operator ? as pure <- rtn as Boolean: celsius? // === MUTATION (NOT pure, no return) === operator += -> other as Temperature celsius += other.celsius operator :=: -> source as Temperature celsius :=: source.celsius defines program CustomTypeDemo() stdout <- Stdout() freezing <- Temperature(0.0) boiling <- Temperature(100.0) body <- Temperature(37.0) // === COMPARISON === stdout.println(`Equal: ${freezing == boiling}`) stdout.println(`Less: ${freezing < boiling}`) stdout.println(`Compare: ${freezing <=> boiling}`) // === ARITHMETIC (creates new values) === sum <- freezing + body stdout.println(`Sum: ${sum}`) diff <- boiling - body stdout.println(`Diff: ${diff}`) // === UNARY === negated <- -body stdout.println(`Negated: ${negated}`) // === PROMOTE === floatVal as Float: body stdout.println(`Promoted: ${floatVal}`) // === CONVERSION === stdout.println(`String: ${body}`) stdout.println(`IsSet: ${body?}`) stdout.println(`Hash: ${#?body}`) // === MUTATION === room <- Temperature(20.0) room += Temperature(5.0) stdout.println(`After +=: ${room}`) target <- Temperature(0.0) target :=: boiling stdout.println(`After copy: ${target}`)
Common mistakes
E07500 — Comparison operators 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 <=> (spaceship) operator must return Integer (negative, zero, or positive for ordering). Returning Boolean instead triggers E07550. See ek9 -h E07550 for details.
Incorrect:
<- rtn as Boolean: celsius <=> other.celsius
Correct:
<- rtn as Integer: celsius <=> other.celsius
E07520 — The ? operator must return Boolean. Returning Integer instead of Boolean triggers E07520. See ek9 -h E07520 for details.
Incorrect:
<- rtn as Integer: celsius?
Correct:
<- rtn as Boolean: celsius?
E50030 — A String literal cannot initialise a Temperature variable. EK9 is strongly typed and requires compatible types. Use the Temperature constructor with a Float argument. See ek9 -h E50030 for details.
Incorrect:
freezing as Temperature: "cold"
Correct:
freezing <- Temperature(0.0)
Other ways to ask this
- What operators should I implement for a custom class in EK9?
- How do I write a complete custom type with all operators?
- What is the best practice for implementing operators on EK9 classes?
Coming from another language?
Java: Comparable, manual equals/hashCode/toString. Python: __eq__/__lt__/__add__ etc. Rust: derive traits. Go: no operator overloading. EK9: operators with enforced signatures, 'default operator' for field-by-field, override for custom.
Keywords: abstract, class, complete, custom, default, example, expression, immutable, implement, mutation, open, operator, override, practice, pure, side-effect, signature, temperature, type, virtual