How do I use constrained types as function parameters for type safety?
← Advanced Type System · Ref: Q721
Constrained types create type-safe function boundaries that eliminate an entire category of invalid-argument bugs at compile time.
DISCONNECTED TYPES ENFORCE API CONTRACTS
When a function takes a constrained type, you cannot pass the raw base type:
printName()
-> name as Name
//...
printName("Alice") //compile error: String is not Name
printName(Name("Alice")) //correct: construct the constrained type
CONSTRUCTION IS THE VALIDATION BOUNDARY
The caller must construct the constrained type, which validates untrusted input via the fallible factory:
if validName <- Name().of(userInput) printName(validName) //guaranteed valid
If the input violates the constraint, of() yields an unset value and the guard's else branch handles it. A bare constructor instead ASSERTS validity — it Panics on a set out-of-range value (and a violating literal is the compile error E08260) — so use of() for untrusted input.
GUARD AT THE BOUNDARY
Use the guard pattern to validate at system entry points:
if age <- DrivingAge().of(rawInput) processDriver(age) //DrivingAge is validated else stdout.println("Invalid age")
Inside processDriver, the DrivingAge is guaranteed to be in range.
CONSTRAINED TYPES IN CLASS PROPERTIES
Classes with constrained type fields enforce validity at construction:
Book
title as BookTitle?
author as AuthorName?
Every Book instance has a validated title and author.
See Q257 for constrained type overview. See Q269 for input validation patterns. See Q722 for type hierarchy.
Example
#!ek9 defines module qa.advancedtypes.constrainedparameters defines type Name as String constrain as matches /^[a-zA-Z -]+$/ DrivingAge as Integer constrain as >= 16 and <= 100 BookTitle as String constrain as matches /^[a-zA-Z0-9 :'-]+$/ defines function <?- Takes a validated Name — cannot accept raw String. -?> formatGreeting() as pure -> name as Name <- greeting as String: `Hello, ${name}!` <?- Takes a validated DrivingAge — cannot accept raw Integer. -?> canRentCar() as pure -> age as DrivingAge <- allowed as Boolean? rentalMinimum <- 21 allowed: age >= rentalMinimum defines program ConstrainedParametersDemo() stdout <- Stdout() // === MUST CONSTRUCT CONSTRAINED TYPE FIRST === validName <- Name("Alice Smith") greeting <- formatGreeting(validName) stdout.println(greeting) // === GUARD PATTERN AT BOUNDARY (fallible factory for untrusted input) === userInput <- "Bob Jones" if checkedName <- Name().of(userInput) result <- formatGreeting(checkedName) stdout.println(result) else stdout.println("Invalid name provided") // === CONSTRAINED TYPE VALIDATES AT THE BOUNDARY === if driverAge <- DrivingAge().of(25) canRent <- canRentCar(driverAge) stdout.println(`Age 25, can rent: ${canRent}`) // === INVALID INPUT IS REJECTED: of() RETURNS UNSET (a bare DrivingAge(10) would Panic) === invalidAge <- DrivingAge().of(10) stdout.println(`Age 10 valid: ${invalidAge?}`) // === CONSTRAINED FIELDS IN RECORDS === title <- BookTitle("The Great Adventure") stdout.println(`Book title: ${title}`) // === MULTIPLE CONSTRAINED PARAMETERS === validTitle <- BookTitle("EK9 Guide") stdout.println(`Title valid: ${validTitle?}`)
Common mistakes
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
greeting <- formatGreeting(validName).toUpperCase()
Correct:
greeting <- formatGreeting(validName)
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
result <- formatGreeting(checkedName).toUpperCase()
Correct:
result <- formatGreeting(checkedName)
Other ways to ask this
- Why should I use constrained types in function signatures?
- Can I pass a String where a Name is expected?
- How do constrained types replace runtime validation?
Coming from another language?
Java: parameter validation via Bean Validation annotations (@Valid, @NotNull), checked at runtime. Python: type hints are advisory, validation with pydantic at runtime. Rust: newtype pattern enforces boundaries but requires manual From/Into implementations. Go: no type constraints, runtime validation in function body. Kotlin: value classes provide some type safety but still subtypes. EK9: constrained types as parameters eliminate invalid-argument bugs at compile time — disconnected types prevent passing raw values, construction validates constraints, guard pattern handles boundary validation.
Keywords: api, boundary, constrained, construction, function, guard, parameter, type-safe, validate