Write me a pure function classifyTemperature that takes a Float celsius and returns a String label, declaring the return variable WITHOUT a default and proving the compiler that every branch of the if/else chain initialises it.

← Data Flow Safety · Ref: Q1249

EK9 has TWO valid patterns for satisfying the compiler's data flow analysis on a return variable: declare with a default value (Pattern 1), or declare without a default and assign in EVERY branch including a final else (Pattern 2). This file demonstrates Pattern 2.

PATTERN 2: NO DEFAULT, MANDATORY ELSE

  classifyTemperature() as pure
    -> celsius as Float
    <- label as String?
    if celsius >= HOT_THRESHOLD
      label: "hot"
    else if celsius >= WARM_THRESHOLD
      label: "warm"
    else if celsius >= COOL_THRESHOLD
      label: "cool"
    else
      label: "cold"

The declaration 'label as String?' creates an UNSET return variable with no initial value. The compiler then requires that every reachable code path assigns 'label' before the function returns. Without the final 'else', a celsius below COOL_THRESHOLD would leave label uninitialised — and the compiler raises E08020.

WHY USE PATTERN 2 INSTEAD OF PATTERN 1

Pattern 2 is preferable when:
1. There is NO sensible default value for the return type.
2. You want the compiler to FORCE you to think about every case.
3. Adding a new branch should be a deliberate decision (no silent fallthrough).

Pattern 1 (default value, see Q1240) is preferable when:
1. There IS a sensible default that should apply to any unhandled case.
2. New branches add refinement to a baseline behaviour.

USAGE

  stdout.println(`30C: ${classifyTemperature(30.0)}`)   // hot
  stdout.println(`22C: ${classifyTemperature(22.0)}`)   // warm
  stdout.println(`15C: ${classifyTemperature(15.0)}`)   // cool
  stdout.println(`-5C: ${classifyTemperature(-5.0)}`)   // cold

KEY POINT

Magic literals (HOT_THRESHOLD, WARM_THRESHOLD, COOL_THRESHOLD) are extracted into a 'defines constant' block to satisfy E11064. Comparing against bare literals is rejected by EK9's quality analysis.

See Q1240 for Pattern 1 (default value). See Q633 for branch initialisation rules. See Q632 for definition order.

Example

defines module qa.dataflow.temperatureclassify

  defines constant

    HOT_THRESHOLD <- 25.0

    WARM_THRESHOLD <- 18.0

    COOL_THRESHOLD <- 10.0

  defines function

    classifyTemperature() as pure
      -> celsius as Float
      <- label as String?
      if celsius >= HOT_THRESHOLD
        label: "hot"
      else if celsius >= WARM_THRESHOLD
        label: "warm"
      else if celsius >= COOL_THRESHOLD
        label: "cool"
      else
        label: "cold"

  defines program

    TemperatureClassifyDemo()
      stdout <- Stdout()

      stdout.println(`30C: ${classifyTemperature(30.0)}`)
      stdout.println(`22C: ${classifyTemperature(22.0)}`)
      stdout.println(`15C: ${classifyTemperature(15.0)}`)
      stdout.println(`-5C: ${classifyTemperature(-5.0)}`)

Common mistakes

E08020 — Without a final else and without a default value at declaration, label is uninitialised when celsius is below WARM_THRESHOLD. Either provide a default at declaration (Pattern 1) or add the final else (Pattern 2). See ek9 -h E08020 for details.

Incorrect:

if celsius >= HOT_THRESHOLD
        label: "hot"
      else if celsius >= WARM_THRESHOLD
        label: "warm"

Correct:

      if celsius >= HOT_THRESHOLD
        label: "hot"
      else if celsius >= WARM_THRESHOLD
        label: "warm"
      else if celsius >= COOL_THRESHOLD
        label: "cool"
Other ways to ask this
  • Show me how to satisfy data flow analysis when there is no default value on the return variable.
  • Create a function whose every branch must explicitly assign the return variable.
  • Implement a temperature classifier that uses an explicit final else to satisfy initialisation analysis.
  • Write a function with no default return where every branch covers initialisation.

Coming from another language?

Java: definite assignment requires every branch to assign — but allows null. Rust: every match arm must assign or use Option. Go: zero-value initialisation always works (but may hide bugs). Python: NameError at runtime. EK9: compile-time enforcement — either Pattern 1 (default) or Pattern 2 (else mandatory), no third option.

Keywords: explicit else, branch initialisation, data flow, no default, E08020, exhaustive, if else chain, return variable