How do I handle multiple preconditions without early return?
← Control Flow Without break/continue/return · Ref: Q285
In Java you write: if (!valid1) return error1; if (!valid2) return error2; doWork();. EK9 has no return. Use structured if/else chains or decompose into validator functions.
THE PROBLEM
Early return guards look clean in small functions but create hidden exits. Each return is a path the reader must trace. Cleanup code can be skipped. In large functions, the number of hidden exits becomes unmanageable.
PATTERN 1: NESTED IF/ELSE CHAIN
Check each condition in sequence with all paths visible:
validate()
-> name as String, age as Integer, email as String
<- message as String: "unknown error"
if length name == 0
message: "name is required"
else if age < 0 or age > 150
message: "age must be between 0 and 150"
else if not (email contains "@")
message: "email must contain @"
else
message: "valid"
Every path is explicit. No hidden exits.
PATTERN 2: VALIDATOR FUNCTIONS WITH GUARDS
Decompose each check into its own function:
validateName() as pure -> name as String <- error as String: String() if length name == 0 error: "name is required"
Then compose with guarded assignment:
problem <- String() problem :=? validateName(name) problem :=? validateAge(age) problem :=? validateEmail(email)
The :=? means the first error wins. Subsequent checks are still evaluated but their results are ignored once problem is set.
PATTERN 3: VALIDATE THEN PROCESS
Separate validation from processing entirely:
isValid() as pure -> name as String, age as Integer <- valid as Boolean: length name > 0 and age >= 0 and age <= 150
Guard on the result:
if isValid(name, age) process(name, age) else reportError()
KEY INSIGHT
Early return creates hidden exits. Explicit if/else makes ALL paths visible. Decomposition into validator functions keeps each check focused and testable.
See Q50 for declared returns. See Q274 for AI return patterns. See Q146 for decomposition. See Q74 for guard expressions.
Example
defines module qa.without.preconditions defines function // Pattern 1: Nested if/else chain validateAll() as pure -> name as String age as Integer email as String <- message as String: "unknown error" maxAge <- 150 if length name == 0 message: "name is required" else if age < 0 or age > maxAge message: "age must be between 0 and 150" else if not (email contains "@") message: "email must contain @" else message: "valid" // Pattern 2: Individual validators validateName() as pure -> name as String <- error as String: String() if length name == 0 error: "name is required" validateAge() as pure -> age as Integer <- error as String: String() maxAge <- 150 if age < 0 or age > maxAge error: "age must be between 0 and 150" validateEmail() as pure -> email as String <- error as String: String() if not (email contains "@") error: "email must contain @" // Pattern 3: Boolean validator isAllValid() as pure -> name as String age as Integer <- valid as Boolean? maxAge <- 150 valid: length name > 0 and age >= 0 and age <= maxAge defines program PreconditionDemo() stdout <- Stdout() // === PATTERN 1: NESTED IF/ELSE === testName <- "Alice" testEmail <- "a@b.com" stdout.println(`Empty name: ${validateAll("", 25, testEmail)}`) stdout.println(`Bad age: ${validateAll(testName, -5, testEmail)}`) stdout.println(`Bad email: ${validateAll(testName, 25, "nope")}`) stdout.println(`All valid: ${validateAll(testName, 25, testEmail)}`) // === PATTERN 2: COMPOSED VALIDATORS === problem <- String() problem :=? validateName(testName) problem :=? validateAge(200) problem :=? validateEmail(testEmail) if problem? stdout.println(`Composed error: ${problem}`) problem2 <- String() problem2 :=? validateName("Bob") problem2 :=? validateAge(30) problem2 :=? validateEmail("b@c.com") if ~problem2? stdout.println("Composed: all valid") // === PATTERN 3: VALIDATE THEN PROCESS === if isAllValid("Charlie", 40) stdout.println("Charlie is valid, processing") else stdout.println("Charlie is invalid") if isAllValid("", 40) stdout.println("Empty name is valid") else stdout.println("Empty name rejected as expected")
Common mistakes
E50001 — A pure function cannot call non-pure methods like stdout.println(). Pure functions must have no side effects. See ek9 -h E50001 for details.
Incorrect:
validateName() as pure -> name as String <- error as String: String() stdout.println(name) if length name == 0 error: "name is required"
Correct:
validateName() as pure -> name as String <- error as String: String() if length name == 0 error: "name is required"
Other ways to ask this
- How do I validate multiple conditions without return in EK9?
- What replaces guard clauses with early return in EK9?
- How do I write a validation chain without return statements in EK9?
Coming from another language?
Java: if (!valid) return error; guard clauses at top of method. Python: if not valid: return error early return guards. Rust: early return with ? operator for Result, pattern matching for validation. Go: if err := validate(); err != nil { return err } very common pattern. Kotlin: require() and check() for preconditions, early return. Swift: guard let for unwrapping, early return. EK9: no return, nested if/else chains, validator functions with :=? composition, validate-then-process decomposition.
Keywords: chain, precondition, no-return, migrate, null-safe, guard, return, early, check, no-break, validation, validate, safe, multiple, isset, error, condition, alternative