What built-in types can be constrained in EK9?
← Advanced Type System · Ref: Q720
EK9 allows constraining types that have comparison operators and meaningful value ranges. Not all types qualify.
CONSTRAINABLE BUILT-IN TYPES
These types can be constrained with 'constrain as' or 'constrain':
String — constrain as matches /pattern/ or equality Integer — constrain as > 0 and < 100 Float — constrain as > 0.0 and < 1.0 Date — constrain as >= 2024-01-01 and <= 2024-12-31 Time — constrain as >= 09:00 and <= 17:00 DateTime — constrain as >= date-time ranges Duration — constrain as >= P1D and <= P30D Millisecond — constrain as >= 0ms and <= 5000ms Dimension — constrain as >= 0m and <= 100m Money — constrain as >= 10000#GBP and <= 120000#GBP Colour — constrain as #0099CC or #9900CC Enumerations — constrain as "Value1" or "Value2"
CONSTRAINABLE USER TYPES
Records and classes with comparison operators can be constrained:
ValidPerson as Person constrain as matches /^[a-zA-Z]+ [a-zA-Z]+$/
The base type must have the operators used in the constraint expression.
NON-CONSTRAINABLE TYPES (E04010)
These types CANNOT be constrained:
Boolean — already has only two values JSON — dynamic structure, undefined comparison semantics Functions — code references, not values Traits — cannot be instantiated Abstract classes — cannot be instantiated Components — singleton services, not value types
WHY THESE RESTRICTIONS
Constraint expressions need comparison operators to evaluate values. Boolean is already maximally constrained. JSON, traits, and abstract classes cannot participate in meaningful value comparisons.
SIMPLE ALIASING IS ALWAYS ALLOWED
Type aliasing without constraints works for any type:
Index as Integer //alias, no constraint Name as String //alias, no constraint
See Q257 for constrained type overview. See Q722 for type hierarchy. See Q721 for constrained types as parameters.
Example
defines module qa.advancedtypes.constrainabletypes defines type // === STRING CONSTRAINT: regex pattern === Name as String constrain as matches /^[a-zA-Z -]+$/ // === INTEGER CONSTRAINT: range === PositiveIndex as Integer constrain as > 0 and < 1000 // === MONEY CONSTRAINT: salary range === Salary as Money constrain as >= 10000#GBP and <= 120000#GBP // === DATE CONSTRAINT: year range === RecentDate as Date constrain as >= 2020-01-01 and <= 2030-12-31 // === COLOUR CONSTRAINT: specific values === BrandColour as Colour constrain as #0099CC or #9900CC // === SIMPLE ALIAS (no constraint) === Index as Integer defines program ConstrainableTypesDemo() stdout <- Stdout() // === STRING CONSTRAINT === // Use the fallible 'of' factory for untrusted input: it returns an UNSET value on a // constraint failure rather than Panicking (a bare Name("123!@#") would Panic at runtime, // and is a compile error E08260 when the bad value is a literal constant). if goodName <- Name().of("Alice Smith") stdout.println(`Valid name: ${goodName}`) badName <- Name().of("123!@#") stdout.println(`Invalid name set: ${badName?}`) // === INTEGER CONSTRAINT === if goodIndex <- PositiveIndex().of(42) stdout.println(`Valid index: ${goodIndex}`) badIndex <- PositiveIndex().of(0) stdout.println(`Zero index set: ${badIndex?}`) // === MONEY CONSTRAINT === if goodSalary <- Salary().of(50000#GBP) stdout.println(`Valid salary: ${goodSalary}`) badSalary <- Salary().of(5000#GBP) stdout.println(`Low salary set: ${badSalary?}`) // === DATE CONSTRAINT === if goodDate <- RecentDate().of(2024-06-15) stdout.println(`Valid date: ${goodDate}`) badDate <- RecentDate().of(2019-01-01) stdout.println(`Old date set: ${badDate?}`) // === COLOUR CONSTRAINT === if goodColour <- BrandColour().of(#0099CC) stdout.println(`Valid colour: ${goodColour}`) badColour <- BrandColour().of(#FF0000) stdout.println(`Wrong colour set: ${badColour?}`) // === SIMPLE ALIAS ALWAYS WORKS === idx <- Index(999) stdout.println(`Alias index: ${idx}`)
Common mistakes
E50060 — Constrained types like Name have no getValue() wrapper method, so calling .getValue() fails to resolve. See ek9 -h E50060 for details.
Incorrect:
Name().of("Alice Smith").getValue()
Correct:
Name().of("Alice Smith")
E50060 — Index has no intValue() method in EK9. Use the promote operator (#^) to extract the Integer value. See ek9 -h E50060 for details.
Incorrect:
idx <- Index(999).intValue()
Correct:
idx <- Index(999)
Other ways to ask this
- Which EK9 types support the constrain keyword?
- Why can't I constrain a Boolean or JSON type?
- What is error E04010 about?
Coming from another language?
Java: no built-in constrained types. Bean Validation annotations (@Min, @Max, @Pattern) are runtime-only. Python: no type-level constraints, runtime validation with pydantic or dataclasses. Rust: no built-in constrained types, uses newtype pattern with constructor validation. Ada: subtype constraints on scalar types only (Integer, Float). Go: no type constraints, runtime validation. Kotlin: value classes with init blocks for runtime validation. EK9: built-in constraint syntax for any type with comparison operators — String (regex/equality), Integer/Float (ranges), Date/Time (ranges), Money (ranges), Colour (values), enumerations (value subsets), records/classes (operator expressions).
Keywords: types, constrain, constrainable, boolean, integer, string, built-in, E04010, date, money