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