How do arithmetic operators work on constrained types?
← Advanced Type System · Ref: Q716
Constrained types inherit arithmetic operators from their base type. Which type the result has depends on the operands.
CT op CT RETURNS THE CONSTRAINED TYPE
When you add two DrivingAge values, the result is DrivingAge, and the constraint is checked on it:
sum <- age1 + age2 //DrivingAge + DrivingAge -> DrivingAge
If that result breaks the constraint (age1 + DrivingAge(90) is 115), the program stops with an uncatchable Panic.
CT op BaseType RETURNS THE BASE TYPE
Mixing in a plain Integer gives a plain Integer, and the constraint is NOT checked on it:
result2 <- age1 + 5 //DrivingAge + Integer -> Integer product <- age1 * 10 //Integer 250, no Panic
So 'check as DrivingAge: age1 + 5' is E50030 (types are not compatible). Wrap the value (DrivingAge(age1 + 5)) when you need it back as the constrained type, and the constraint is checked then.
MUTATING OPERATORS
Compound assignment and increment keep the variable a DrivingAge, and the constraint is checked after each one:
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}`) // CAUTION: the RESULT must satisfy the constraint too. There is deliberately no // 'age2 - age1' here - that is 5, below the minimum of 16, and a breach is an // uncatchable Panic rather than an unset value. A gap between two ages is not an age. // === MIXED: CT + BaseType -> BaseType (Integer, constraint NOT checked) === 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}`) addValue <- 20 mutable += DrivingAge(addValue) stdout.println(`22 += 20 = ${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 (CT op CT -> CT, constraint checked; CT op BaseType -> BaseType, unchecked), mutating operators keep the constrained type and check it, mod/rem return base type.
Keywords: integer, subtract, constrained, increment, range, compound, mutating, operator, add, arithmetic