How do arithmetic operators work on constrained types?
← Advanced Type System · Ref: Q716
Constrained types inherit arithmetic operators from their base type, with results returned as the constrained type.
ARITHMETIC RETURNS THE CONSTRAINED TYPE
When you add two DrivingAge values, the result is DrivingAge:
sum <- age1 + age2
The compiler generates dual-signature operators: DrivingAge + DrivingAge and DrivingAge + Integer both work.
DUAL SIGNATURES
Every arithmetic operator gets two forms:
result1 <- age1 + age2 //DrivingAge + DrivingAge -> DrivingAge result2 <- age1 + 5 //DrivingAge + Integer -> DrivingAge
This lets you work with constrained types naturally without wrapping literals.
MUTATING OPERATORS
Compound assignment and increment delegate to the base type:
age1 += 5 //DrivingAge += Integer age1 -= age2 //DrivingAge -= DrivingAge age1++ //increment age1-- //decrement
MOD AND REM RETURN BASE TYPE
The mod and rem operators return the base type (Integer), not the constrained type:
modResult <- age1 mod 7 //returns Integer, not DrivingAge remResult <- age1 rem 7 //returns Integer, not DrivingAge
ONLY BASE TYPE OPERATORS
A constrained type can only use operators defined on its base type. If you constrain a record that has no + operator, you cannot add constrained values.
See Q257 for constrained type overview. See Q717 for comparison operators. See Q720 for which types can be constrained.
Example
defines module qa.advancedtypes.constrainedarithmetic defines type DrivingAge as Integer constrain as >= 16 and <= 100 defines program ConstrainedArithmeticDemo() stdout <- Stdout() // === ARITHMETIC: CT + CT -> CT === age1 <- DrivingAge(25) age2 <- DrivingAge(30) sum <- age1 + age2 stdout.println(`25 + 30 = ${sum}`) diff <- age2 - age1 stdout.println(`30 - 25 = ${diff}`) // === DUAL SIGNATURES: CT + BaseType -> CT === incremented <- age1 + 5 stdout.println(`25 + 5 = ${incremented}`) decremented <- age2 - 10 stdout.println(`30 - 10 = ${decremented}`) product <- age1 * 2 stdout.println(`25 * 2 = ${product}`) // === MOD AND REM RETURN BASE TYPE (Integer) === modResult <- age1 mod 7 stdout.println(`25 mod 7 = ${modResult}`) remResult <- age1 rem 7 stdout.println(`25 rem 7 = ${remResult}`) // === MUTATING OPERATORS === mutable <- DrivingAge(20) mutable += 5 stdout.println(`20 += 5 = ${mutable}`) mutable -= 3 stdout.println(`25 -= 3 = ${mutable}`) tooYoungValue <- 10 mutable += DrivingAge(tooYoungValue) stdout.println(`22 += 10 = ${mutable}`) // === INCREMENT AND DECREMENT === counter <- DrivingAge(40) counter++ stdout.println(`40++ = ${counter}`) counter-- stdout.println(`41-- = ${counter}`)
Common mistakes
E50060 — DrivingAge has no intValue() method in EK9. Constrained types do not have extra methods beyond those inherited from the base type. See ek9 -h E50060 for details.
Incorrect:
sum <- age1.intValue() + age2
Correct:
sum <- age1 + age2
E50060 — DrivingAge has no intValue() method in EK9. Use the promote operator (#^) to extract the base type value. See ek9 -h E50060 for details.
Incorrect:
incremented <- age1.intValue() + 5
Correct:
incremented <- age1 + 5
Other ways to ask this
- Can I do math with constrained Integer types?
- What happens when I add two constrained type values?
- Do constrained types support increment and compound assignment?
Coming from another language?
Java: no constrained types, arithmetic on wrapper types requires unboxing. Python: no type-level constraints, arithmetic on custom classes via __add__. Rust: newtype pattern requires explicit Deref or operator trait implementations. Go: type aliases share operators but no value constraints. Kotlin: value classes wrap a single value but require manual operator definitions. EK9: constrained types automatically inherit all arithmetic operators from the base type with dual signatures (CT+CT and CT+BaseType), mutating operators delegate to base, mod/rem return base type.
Keywords: range, increment, constrained, subtract, integer, arithmetic, add, operator, mutating, compound