Write me a pure function categorise that takes an Integer score and returns a String category ('high', 'medium' or 'low'), proving the return variable is initialised on every branch.
← Data Flow Safety · Ref: Q1240
EK9 tracks the initialisation state of every variable through all control flow paths. A return variable can be made provably initialised in TWO ways: provide a default value at declaration time, or assign in EVERY branch of every conditional.
PATTERN 1: DEFAULT VALUE AT DECLARATION (RECOMMENDED)
The return variable starts with a sensible default. Branches override it as needed:
categorise() as pure -> score as Integer <- rtn as String: "low" if score >= 80 rtn: "high" else if score >= 50 rtn: "medium"
The compiler accepts this because 'rtn' is initialised before any branch runs. The if/else only modifies an already-set value.
PATTERN 2: EXPLICIT ASSIGNMENT ON EVERY BRANCH
Declare the return variable without a default but assign in every branch:
categorise() as pure -> score as Integer <- rtn as String? if score >= 80 rtn: "high" else if score >= 50 rtn: "medium" else rtn: "low"
The final 'else' is required — without it, a score below 50 would leave 'rtn' uninitialised and the compiler raises E08020.
WHY DEFAULT IS PREFERRED
Pattern 1 is more robust: adding a new branch (e.g. 'if score >= 95 then "excellent"') does not risk leaving rtn uninitialised because the default still applies to any path that does not override it. Pattern 2 requires updating the else clause whenever cases change.
USAGE
stdout.println(categorise(95)) // "high" stdout.println(categorise(70)) // "medium" stdout.println(categorise(30)) // "low"
See Q633 for branch initialisation rules. See Q632 for definition order. See Q634 for guard-based safe access.
Example
defines module qa.dataflow.categorise defines constant HIGH_THRESHOLD <- 80 MEDIUM_THRESHOLD <- 50 defines function categorise() as pure -> score as Integer <- rtn as String: "low" if score >= HIGH_THRESHOLD rtn: "high" else if score >= MEDIUM_THRESHOLD rtn: "medium" defines program CategoriseDemo() stdout <- Stdout() stdout.println(`Score 95: ${categorise(95)}`) stdout.println(`Score 70: ${categorise(70)}`) stdout.println(`Score 30: ${categorise(30)}`)
Common mistakes
E08020 — Without a default value AND without a final else, rtn is uninitialised when score < 50. Either provide a default at declaration or add an else branch that assigns rtn. See ek9 -h E08020 for details.
Incorrect:
<- rtn as String? if score >= 80 rtn: "high" else if score >= 50 rtn: "medium"
Correct:
<- rtn as String: "low" if score >= HIGH_THRESHOLD rtn: "high" else if score >= MEDIUM_THRESHOLD rtn: "medium"
Other ways to ask this
- Create a function whose return value is provably set on every if/else branch.
- Show me how to satisfy EK9 data flow analysis with branch initialisation.
- Implement a categoriser that the compiler can prove always returns a value.
- Write a function with a default-initialised return variable for safety.
Coming from another language?
Java: definite assignment analysis catches some of these but allows null. Rust: must assign on every path or use Option<T>. Go: zero-value initialisation always works but may hide bugs. Python: NameError at runtime. EK9: compile-time guarantee — either declare with default or assign on every branch, no third option.
Keywords: initialisation, data flow, uninitialised, branch, default value, E08020, if else, return variable