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: == (equality), $ (string), ? (isSet).
COMPARISON (pure, 1 arg, return Boolean or Integer)
operator == as pure -> other as T <- rtn as Boolean operator <=> as pure -> other as T <- rtn as Integer
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
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 (pure, 1 arg, return Boolean or Integer) === 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 // === 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 equality checks 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 == other.celsius
Correct:
<- rtn as Boolean: celsius == other.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: immutable, expression, side-effect, type, practice, abstract, complete, implement, custom, temperature, operator, virtual, open, mutation, signature, override, class, example, default, pure