How do comparison operators work between constrained types and their base types?
← Advanced Type System · Ref: Q717
Constrained types inherit comparison operators from the base type with dual signatures: each operator works with both the constrained type and the raw base type.
DUAL SIGNATURE PATTERN
Every comparison operator gets two forms:
isEqual1 <- age1 == age2 //DrivingAge == DrivingAge -> Boolean isEqual2 <- age1 == twentyFive //DrivingAge == Integer -> Boolean
This means you can compare constrained values directly with values of the base type. A bare numeric literal such as 25 in a comparison is rejected by E11064 (give the literal a meaningful name first, as twentyFive <- 25 does); that rule is about numeric literals, so a String-constrained type can still be compared with a literal such as "Alice".
WHY DUAL SIGNATURES MATTER
Without dual signatures, you would need to wrap every value, as in 'age1 == DrivingAge(25)' (works but verbose), whereas 'age1 == twentyFive' with a named Integer is much cleaner.
The compiler generates both forms automatically.
ALL COMPARISON OPERATORS
The full set of inherited comparisons (when base type has them):
== equality <> not equal < less than <= less or equal > greater than >= greater or equal <=> spaceship (returns Integer)
OPERATORS FROM BASE TYPE ONLY
If the base type lacks an operator, the constrained type does not have it either. A record with only == and <> will not support <, <=, >, >= on its constrained type.
CONTAINS AND MATCHES
String-constrained types inherit contains and matches:
hasAlice <- name1 contains "Alice" //String has contains matched <- name1 matches /^A.*/ //String has matches
Note that matches tests the WHOLE string against the pattern, so /^A/ alone would be false for Alice while /^A.*/ is true.
Integer-constrained types do NOT have these.
See Q257 for constrained type overview. See Q716 for arithmetic operators. See Q718 for the promote operator.
Example
#!ek9 defines module qa.advancedtypes.constrainedcomparisons defines type DrivingAge as Integer constrain as >= 16 and <= 100 Name as String constrain as matches /^[a-zA-Z -]+$/ defines program ConstrainedComparisonDemo() stdout <- Stdout() // === EQUALITY: CT == CT and CT == BaseType === age1 <- DrivingAge(25) age2 <- DrivingAge(30) isEqual <- age1 == age2 stdout.println(`25 == 30: ${isEqual}`) twentyFive <- 25 isEqualBase <- age1 == twentyFive stdout.println(`25 == 25: ${isEqualBase}`) // === NOT EQUAL === notEqual <- age1 <> age2 stdout.println(`25 <> 30: ${notEqual}`) ninetyNine <- 99 notEqualBase <- age1 <> ninetyNine stdout.println(`25 <> 99: ${notEqualBase}`) // === ORDERING: CT < CT and CT < BaseType === isLess <- age1 < age2 stdout.println(`25 < 30: ${isLess}`) fifty <- 50 isLessBase <- age1 < fifty stdout.println(`25 < 50: ${isLessBase}`) isGreater <- age2 > age1 stdout.println(`30 > 25: ${isGreater}`) twenty <- 20 isGreaterBase <- age2 > twenty stdout.println(`30 > 20: ${isGreaterBase}`) // === SPACESHIP OPERATOR === cmpResult <- age1 <=> age2 stdout.println(`25 <=> 30: ${cmpResult}`) // === STRING-CONSTRAINED: contains and matches === name1 <- Name("Alice") name2 <- Name("Bob") same <- name1 == name2 stdout.println(`Alice == Bob: ${same}`) sameBase <- name1 == "Alice" stdout.println(`Alice == 'Alice': ${sameBase}`) matched <- name1 matches /^A.*/ stdout.println(`Alice matches ^A.*: ${matched}`)
Common mistakes
E50060 — DrivingAge has no equals() method in EK9. Use the == operator for equality comparison. See ek9 -h E50060 for details.
Incorrect:
isEqual <- age1.equals(age2)
Correct:
isEqual <- age1 == age2
E50060 — Name has no equals() method in EK9. Use the == operator for equality comparison. See ek9 -h E50060 for details.
Incorrect:
sameBase <- name1.equals("Alice")
Correct:
sameBase <- name1 == "Alice"
Other ways to ask this
- Can I compare a constrained type with its base type value?
- Why does DrivingAge < Integer work?
- What comparison operators does a constrained type inherit?
Coming from another language?
Java: no constrained types, comparisons use equals()/compareTo() methods. Python: no type constraints, comparison via __eq__/__lt__ etc. Rust: newtype pattern requires implementing PartialEq/PartialOrd traits manually. Go: type aliases inherit comparison operators but no value constraints. Kotlin: value classes require manual operator definitions. EK9: constrained types automatically inherit all comparison operators from the base type with dual signatures (CT==CT and CT==BaseType), enabling natural comparisons with both constrained values and base-type values (numeric literals still need a name, E11064).
Keywords: comparison, constrained, contains, dual, equals, inherit, matches, operator, signature