Question: How do if/else statements work in EK9?

How do I write a conditional in EK9?
What is the if statement syntax in EK9?
How do I test a condition in EK9?

Answer:
EK9's if/else works like most languages but with a few differences: no parentheses around conditions, indentation-based blocks (no braces), and no 'elif' keyword (use 'else if' as two words).

BASIC IF
The simplest form tests a condition and executes the indented body:
  if temperature > 30
    stdout.println("Hot day")
No parentheses. No braces. Just condition and indented body.

IF/ELSE
Add an else branch for the alternative path:
  if balance > 0
    status: "positive"
  else
    status: "overdrawn"
The else keyword sits at the same indentation as the if keyword.

IF/ELSE-IF/ELSE
Chain conditions with 'else if' (two words, never 'elif'):
  if score >= 90
    grade: "A"
  else if score >= 80
    grade: "B"
  else if score >= 70
    grade: "C"
  else
    grade: "F"
Conditions are evaluated top to bottom. The first match wins.

CONDITION TYPES
Any expression that produces a Boolean can be a condition:
  if ready                         single Boolean variable
  if count > 0                     comparison
  if name == "Admin"               equality
  if age >= 18 and hasConsent      compound with 'and'
  if isActive or isAdmin           compound with 'or'
  if not finished                  negation

ALTERNATIVE KEYWORD: WHEN
EK9 allows 'when' as an alternative to 'if'. They are interchangeable:
  when temperature > 30
    stdout.println("Hot day")
This is purely stylistic. Use whichever reads better in context.

WHAT IF/ELSE DOES NOT DO IN EK9
EK9's if is a statement, not an expression. You cannot write:
  result <- if condition then "yes" else "no"
For expression-based branching, use switch as an expression (see Q68).

GUARDS AND MORE
EK9's if supports guard variables that combine assignment with condition checking. This is covered in Q74. Guards are what make EK9's if truly powerful, but the basic form shown here works exactly as you would expect from any language.

See Q29 for how unset values interact with conditions. See Q30 for Boolean operators (and, or, xor, not). See Q62 for complex if/else-if chaining patterns. See Q72 for the 'when' keyword in other contexts. See Q74 for guard variables in if statements.

defines module qa.flow.conditional

  defines function

    supplyTemperature() as pure
      <- rtn <- 35

    supplyBalance() as pure
      <- rtn <- -50

    supplyScore() as pure
      <- rtn <- 85

    supplyAge() as pure
      <- rtn <- 25

    supplyLicenseStatus() as pure
      <- rtn <- true

    supplyAdminStatus() as pure
      <- rtn <- false

    supplyOwnerStatus() as pure
      <- rtn <- true

    supplyFinishedStatus() as pure
      <- rtn <- false

  defines program

    IfElseDemo()
      stdout <- Stdout()

      // === BASIC IF ===

      temperature <- supplyTemperature()
      temperatureThreshold <- 30
      if temperature > temperatureThreshold
        stdout.println("Hot day")

      // === IF/ELSE ===

      balance <- supplyBalance()
      status <- String()
      if balance > 0
        status: "positive"
      else
        status: "overdrawn"
      stdout.println(`Balance status: ${status}`)

      // === IF/ELSE-IF/ELSE ===

      score <- supplyScore()
      grade <- String()
      excellentScore <- 90
      goodScore <- 80
      passingScore <- 70
      if score >= excellentScore
        grade: "A"
      else if score >= goodScore
        grade: "B"
      else if score >= passingScore
        grade: "C"
      else
        grade: "F"
      stdout.println(`Score ${score} is grade ${grade}`)

      // === COMPOUND CONDITIONS ===

      age <- supplyAge()
      hasLicense <- supplyLicenseStatus()
      adultAge <- 18
      if age >= adultAge and hasLicense
        stdout.println("Can drive")

      isAdmin <- supplyAdminStatus()
      isOwner <- supplyOwnerStatus()
      if isAdmin or isOwner
        stdout.println("Has access")

      // === NEGATION ===

      finished <- supplyFinishedStatus()
      if not finished
        stdout.println("Still working")

      // === WHEN (alternative keyword) ===

      hotThreshold <- 30
      when temperature > hotThreshold
        stdout.println("When says: hot day")

Java: if (condition) { } else if { } else { } with parentheses and braces required. Python: if/elif/else with colon and indentation, 'elif' keyword. Rust: if condition { } else if { } else { } with braces required, if is an expression. Go: if condition { } else if { } else { } with braces required, can declare variable in if. Kotlin: if/else is an expression, can return values. C++: if (condition) { } else if { } else { } with parentheses and braces. JavaScript: if (condition) { } else if { } else { } with parentheses and braces. C#: if (condition) { } else if { } else { } with parentheses and braces. Swift: if condition { } else if { } else { } no parentheses, braces required. EK9: if/else with indentation, no parentheses, no braces, no elif (use 'else if'), 'when' as alternative keyword, guard variables for safe null checking.

boolean branch check comparison condition conditional control else flow if test when

if else
if statement
conditional
branching

if temperature > temperatureThreshold
        stdout.println("Hot day")
        return

Explanation: EK9 has no return statement. The keyword does not exist in the grammar. Use declared return variables and guard expressions instead. See ek9 -h E01072 for details.


Question: How do I chain multiple if/else-if conditions?

How do I write multiple condition branches in EK9?

Answer:
EK9 uses 'else if' as two separate words for chaining conditions. There is no 'elif' keyword (Python) or 'elsif' (Ruby/Perl). This is a deliberate choice for readability and consistency.

BASIC CHAIN
Conditions are evaluated top to bottom. The first matching branch executes:
  if score >= 90
    grade: "A"
  else if score >= 80
    grade: "B"
  else if score >= 70
    grade: "C"
  else if score >= 60
    grade: "D"
  else
    grade: "F"
Only one branch ever executes. Once a condition matches, the remaining branches are skipped.

COMPOUND CONDITIONS IN CHAINS
Each branch can use compound Boolean expressions:
  if age < 13
    category: "child"
  else if age < 18
    category: "teenager"
  else if age < 65 and isEmployed
    category: "working adult"
  else if age >= 65
    category: "retired"
  else
    category: "adult"

NO ELIF KEYWORD
Some developers coming from Python expect 'elif'. EK9 does not have this keyword. Using 'elif' will produce a compiler error. Always use 'else if' as two words.

WHEN TO USE SWITCH INSTEAD
If you are matching a single variable against specific values, consider switch instead of a long if/else-if chain:
  switch day
    case "Monday"
      action: "start of week"
    case "Friday"
      action: "end of week"
    default
      action: "mid week"
Switch is often clearer when testing one value against multiple possibilities. See Q63 for switch syntax. For comparison operators in switch cases (case < 12, case > 100), see Q70.

See Q61 for basic if/else syntax. See Q63 for switch as an alternative. See Q72 for 'when' as an alternative keyword.

defines module qa.flow.chained.conditions

  defines function

    classifyAge() as pure
      -> ageInput as Integer
      <- rtn as Integer: ageInput

    checkEmployment() as pure
      -> employed as Boolean
      <- rtn as Boolean: employed

    classifyTemperature() as pure
      -> celsius as Float
      <- description as String: "unknown"

      lowTemp <- 10.0
      mildTemp <- 20.0
      warmTemp <- 30.0
      hotTemp <- 40.0

      if celsius < 0.0
        description: "freezing"
      else if celsius < lowTemp
        description: "cold"
      else if celsius < mildTemp
        description: "cool"
      else if celsius < warmTemp
        description: "warm"
      else if celsius < hotTemp
        description: "hot"
      else
        description: "extreme"

  defines program

    IfChainDemo()
      stdout <- Stdout()

      // === GRADE CLASSIFICATION ===

      scores <- [95, 82, 73, 61, 45]
      for score in scores
        grade <- String()
        excellent <- 90
        good <- 80
        average <- 70
        belowAverage <- 60
        if score >= excellent
          grade: "A"
        else if score >= good
          grade: "B"
        else if score >= average
          grade: "C"
        else if score >= belowAverage
          grade: "D"
        else
          grade: "F"
        stdout.println(`Score ${score} -> Grade ${grade}`)

      // === TEMPERATURE CLASSIFICATION (function) ===

      temperatures <- [Float(-5.0), Float(8.0), Float(15.0), Float(25.0), Float(38.0)]
      for temp in temperatures
        stdout.println(`${temp}C is ${classifyTemperature(temp)}`)

      // === COMPOUND CONDITIONS ===

      age <- classifyAge(30)
      isEmployed <- checkEmployment(true)
      category <- String()
      teenAge <- 13
      adultAge <- 18
      seniorAge <- 65
      if age < teenAge
        category: "child"
      else if age < adultAge
        category: "teenager"
      else if age < seniorAge and isEmployed
        category: "working adult"
      else if age >= seniorAge
        category: "retired"
      else
        category: "adult"
      stdout.println(`Age ${age}: ${category}`)

Java: else if with braces, no special keyword. Python: elif keyword (single word). Rust: else if with braces, if is an expression so match often preferred. Go: else if with braces, no special keyword. Ruby: elsif keyword. Perl: elsif keyword. Kotlin: else if, or 'when' expression for multi-branch. C#: else if with braces. JavaScript: else if with braces. Swift: else if with braces. EK9: else if (two words), no elif/elsif keyword, indentation-based blocks.

branch cascade chain condition conditions control elif else elsif flow if migrate multiple

if score >= excellent
          return "A"

Explanation: EK9 has no return statement. Assign to a pre-declared variable in each branch instead. The compiler verifies all paths initialise the variable. See ek9 -h E01072 for details.


Question: How does switch/case work in EK9?

What is EK9's equivalent of switch or match?

How do I match a value against multiple options in EK9?

Answer:
EK9's switch matches a value against multiple cases. It looks familiar but has important differences from other languages: no break statements needed, no fallthrough between cases, and the ability to match multiple values per case.

BASIC SWITCH
Match a value against literal cases:
  switch status
    case 1
      message: "active"
    case 2
      message: "inactive"
    case 3
      message: "pending"
    default
      message: "unknown"
Each case runs its body and stops. There is no fallthrough and no break keyword.

STRING MATCHING
Switch works with any type that supports equality:
  switch command
    case "start"
      doStart()
    case "stop"
      doStop()
    default
      showHelp()

DEFAULT CASE
The default block handles any value not matched by a case. It is optional but recommended for safety:
  switch direction
    case "north"
      y: y + 1
    case "south"
      y: y - 1
    default
      stdout.println("Unhandled direction")

NO BREAK NEEDED
In Java and C, forgetting 'break' causes fallthrough bugs. EK9 eliminates this entirely. Each case is self-contained. There is no break keyword in EK9 (see Q144 for why).

NO FALLTHROUGH
If you need multiple values to execute the same code, use comma-separated case values instead of fallthrough (see Q69):
  switch day
    case "Saturday", "Sunday"
      type: "weekend"
    default
      type: "weekday"

MORE POWER
Switch in EK9 goes far beyond simple literal matching:
- Return values as an expression (see Q68)
- Multiple values per case (see Q69)
- Comparison operators like case < 12 (see Q70)
- Pattern matching with regex (see Q71)
- Alternative keywords given/when (see Q72)
- Exhaustive enum matching (see Q73)
- Guard variables for safe access (see Q74)

See Q61 for if/else as an alternative. See Q62 for when if/else-if chains might be better. See Q68 for switch expression. See Q73 for enum switch.

defines module qa.flow.branching

  defines function

    supplyStatusCode()
      <- rtn <- 2

    supplyCommand()
      <- rtn <- "greet"

    supplyDay()
      <- rtn <- "Saturday"

  defines program

    SwitchDemo()
      stdout <- Stdout()

      // === BASIC SWITCH WITH INTEGERS ===

      statusCode <- supplyStatusCode()
      message <- String()
      switch statusCode
        case 1
          message: "active"
        case 2
          message: "inactive"
        case 3
          message: "pending"
        default
          message: "unknown"
      stdout.println(`Status ${statusCode}: ${message}`)

      // === SWITCH WITH STRINGS ===

      command <- supplyCommand()
      switch command
        case "greet"
          stdout.println("Hello!")
        case "farewell"
          stdout.println("Goodbye!")
        default
          stdout.println("Unknown command")

      // === MULTIPLE VALUES PER CASE ===

      day <- supplyDay()
      dayType <- String()
      switch day
        case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
          dayType: "weekday"
        case "Saturday", "Sunday"
          dayType: "weekend"
        default
          dayType: "invalid"
      stdout.println(`${day} is a ${dayType}`)

      // === SWITCH IN A LOOP ===

      codes <- [1, 2, 3, 4, 5]
      for code in codes
        label <- String()
        switch code
          case 1, 2
            label: "low"
          case 3
            label: "medium"
          case 4, 5
            label: "high"
          default
            label: "off scale"
        stdout.println(`Code ${code} -> ${label}`)

Java: switch with break required (fallthrough by default), switch expressions since Java 14 with arrow syntax. Python: match/case since 3.10, structural pattern matching (different from simple value matching). Rust: match is exhaustive, is an expression, pattern matching with destructuring. Go: switch with no fallthrough by default (opposite of C/Java), implicit break. C/C++: switch with break required, fallthrough by default, major bug source. Kotlin: when expression replaces switch, no fallthrough, exhaustive with sealed classes. C#: switch with break required, switch expressions since C# 8. JavaScript: switch with break required, fallthrough by default. Swift: switch is exhaustive, no fallthrough, pattern matching. EK9: switch with no break keyword, no fallthrough, comma-separated multi-case values, comparison operators in cases, given/when alternative keywords.

branch break case condition control default fallthrough flow given literal match migrate switch value

switch statusCode
        case 1
          message: "active"
          break
        case 2
          message: "inactive"
          break

Explanation: EK9 has no break statement. Switch cases do not fall through, so break is unnecessary and does not exist in the grammar. Each case is self-contained. See ek9 -h E01070 for details.

switch command
        case "greet"
          stdout.println("Hello!")
          return
        case "farewell"
          stdout.println("Goodbye!")
          return

Explanation: EK9 has no return statement. In a program, execution flows naturally to the end. In functions, use a declared return variable instead. See ek9 -h E01072 for details.


Question: How do I write a for loop over a range of numbers?

How do I loop over a range of numbers in EK9?

What is the for loop syntax for counting in EK9?

How do range-based for loops work in EK9?

What are the basic loop constructs in EK9?

Answer:
EK9 has a for-range loop that iterates over a numeric range using the '...' (ellipsis) operator. The range is inclusive on both ends.

BASIC INTEGER RANGE
Count from 1 to 10 (inclusive):
  for i in 1 ... 10
    stdout.println($i)
The variable i is implicitly declared as Integer. Both start and end values are included.

CUSTOM STEP WITH BY
Use 'by' to specify the step size:
  for i in 0 ... 20 by 5
    stdout.println($i)
This prints 0, 5, 10, 15, 20. The step can be any positive integer.

DESCENDING RANGE
Count downward by specifying a negative step:
  for i in 10 ... 0 by -1
    stdout.println($i)
This counts from 10 down to 0. The negative step is required for descending ranges.

EVEN NUMBERS
Combine start value and step:
  for i in 2 ... 20 by 2
    stdout.println($i)
Prints 2, 4, 6, 8, 10, 12, 14, 16, 18, 20.

VARIABLE BOUNDS
The start and end values can be variables or expressions:
  limit <- 100
  for i in 1 ... limit
    total: total + i

FLOAT RANGES
For-range works with Float values:
  for f in 0.0 ... 1.0 by 0.25
    stdout.println($f)
Prints 0.0, 0.25, 0.5, 0.75, 1.0.

DURATION AND TIME RANGES
For-range also works with Duration and Time types, making it easy to iterate over time intervals:
  thirtyMinutes <- PT30M
  start <- PT0S
  end <- PT2H
  for d in start ... end by thirtyMinutes
    stdout.println($d)
See Q31 for Date/Time types and Q32 for Duration.

NO BREAK
EK9 has no break statement. For-range loops always run from start to end. If you need to stop early, use a stream pipeline with 'head' instead (see Q125). For-range can also return values as an expression (see Q80).

See Q39 for Integer and Float types. See Q65 for iterating over collections (for-in). See Q80 for using for-range as an expression. See Q144 for why there is no break. See Q89 for stream pipelines as loop alternatives. See Q125 for head as the only early exit mechanism. See Q83 for loop expression overview. See Q283 for retry logic using for-range.

defines module qa.flow.range.loop

  defines program

    ForRangeDemo()
      stdout <- Stdout()

      // === BASIC INTEGER RANGE ===

      stdout.println("Counting 1 to 5:")
      for i in 1 ... 5
        stdout.println(`  ${i}`)

      // === CUSTOM STEP ===

      stdout.println("Even numbers 2 to 10:")
      for i in 2 ... 10 by 2
        stdout.println(`  ${i}`)

      // === DESCENDING RANGE ===

      stdout.println("Countdown 5 to 1:")
      for i in 5 ... 1 by -1
        stdout.println(`  ${i}`)

      // === SUM WITH ACCUMULATION ===

      total <- 0
      for i in 1 ... 100
        total: total + i
      stdout.println(`Sum 1..100: ${total}`)

      // === VARIABLE BOUNDS ===

      start <- 3
      end <- 7
      stdout.println(`Range ${start} to ${end}:`)
      for i in start ... end
        stdout.println(`  ${i}`)

      // === FLOAT RANGE ===

      stdout.println("Float range 0.0 to 1.0:")
      for f in 0.0 ... 1.0 by 0.25
        stdout.println(`  ${f}`)

      // === NESTED RANGES ===

      stdout.println("Multiplication table (1-3):")
      for row in 1 ... 3
        for col in 1 ... 3
          stdout.println(`  ${row} x ${col} = ${row * col}`)

Java: for (int i = 0; i < 10; i++) with C-style syntax, verbose. Python: for i in range(1, 11) with exclusive end, range() function. Rust: for i in 1..=10 with ..= for inclusive, .. for exclusive. Go: for i := 0; i < 10; i++ with C-style syntax. C++: for (int i = 0; i < 10; ++i) with C-style syntax. Kotlin: for (i in 1..10) with .. operator, downTo for descending, step for custom increment. Swift: for i in 1...10 with ... for inclusive, ..< for exclusive. C#: for (int i = 0; i < 10; i++) with C-style syntax, Enumerable.Range(). EK9: for i in 1 ... 10 with ... operator (inclusive both ends), 'by' keyword for step, works with Integer, Float, Duration, and Time types.

ascending branch by condition control count descending ellipsis float flow for integer iterate loop number range step

for loop
for range
counting loop
loop over numbers

for i in 1 ... 5
        stdout.println(`  ${i}`)
        if i == 3
          break

Explanation: EK9 has no break statement. For-range loops always run from start to end. Use stream pipelines with head to limit items instead. See ek9 -h E01070 for details.

for i in 2 ... 10 by 2
        if i == 6
          continue
        stdout.println(`  ${i}`)

Explanation: EK9 has no continue statement. Use stream pipelines with filter to skip unwanted items instead of continue. See ek9 -h E01071 for details.


Question: How do I use a for loop to iterate over a collection?

How do I iterate over a dictionary in EK9?

How do collection-based for loops work in EK9?

How do I use a for loop to iterate over items?

Answer:
EK9's for-in loop iterates over any collection or iterable. The syntax is 'for item in collection'. This is the EK9 equivalent of Java's enhanced for, Python's for-in, and Rust's for-in.

LIST ITERATION
Iterate over each element in a list:
  fruits <- ["apple", "banana", "cherry"]
  for fruit in fruits
    stdout.println(fruit)
The loop variable 'fruit' is implicitly declared with the element type.

DICT ITERATION
Iterate over dictionary entries:
  ages <- {"Alice": 30, "Bob": 25}
  for entry in ages
    stdout.println($entry)
Each entry is a DictEntry with key and value properties. See Q46 for Dict details.

ITERATOR PATTERN
You can also iterate using an explicit iterator:
  items <- ["one", "two", "three"]
  iter <- items.iterator()
  while iter?
    stdout.println(iter.next())
The ? operator checks if the iterator has more items (isSet). See Q66 for while loop details.

FOR-IN VS FOR-RANGE
For-in iterates over a collection's elements. For-range iterates over a numeric range:
  for item in myList        for-in: each element
  for i in 1 ... 10         for-range: each number
Use for-in when you have a collection. Use for-range when you need a counting loop (see Q64).

WORKS WITH ANY ITERABLE
For-in works with any type that provides an iterator. This includes List, Dict, and any user-defined type that implements the iterator pattern.

NO BREAK OR CONTINUE
For-in loops always process every element. There is no break to stop early and no continue to skip elements. If you need to filter or limit items, use a stream pipeline instead:
  cat fruits | filter by isLong > stdout
See Q86 for early exit alternatives and Q87 for skip alternatives.

For-in can also return values as an expression (see Q80). Guard variables can be used in for-in (see Q74).

See Q45 for List type details. See Q46 for Dict type details. See Q64 for for-range loops. See Q89 for stream pipelines as alternatives to for-in loops. See Q120 for sorting streams. See Q125 for head as the only early exit mechanism.

defines module qa.flow.collection.loop

  defines function

    isLong() as pure
      -> text as String
      <- rtn <- false
      expectedCount <- 4
      rtn: length text > expectedCount

  defines program

    ForInDemo()
      stdout <- Stdout()

      // === LIST ITERATION ===

      fruits <- ["apple", "banana", "cherry", "date"]
      stdout.println("Fruits:")
      for fruit in fruits
        stdout.println(`  ${fruit}`)

      // === LIST OF NUMBERS ===

      numbers <- [10, 20, 30, 40, 50]
      total <- 0
      for num in numbers
        total: total + num
      stdout.println(`Sum: ${total}`)

      // === DICT ITERATION ===

      ages <- {"Alice": 30, "Bob": 25, "Charlie": 35}
      stdout.println("Ages:")
      for entry in ages
        stdout.println(`  ${entry}`)

      // === NESTED ITERATION ===

      matrix <- [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
      stdout.println("Matrix:")
      for row in matrix
        for item in row
          stdout.println(`  ${item}`)

      // === STREAM ALTERNATIVE (filter) ===

      stdout.println("Long fruit names (>4 chars):")
      cat fruits | filter by isLong > stdout

Java: for (String item : list) enhanced for loop, or list.forEach(item -> ...) with lambda. Python: for item in list with direct iteration, enumerate() for index+value. Rust: for item in &list with borrowing semantics, .iter() for references, .into_iter() for ownership. Go: for _, item := range list with blank identifier for index, for k, v := range map for maps. C++: for (auto& item : list) range-based for since C++11. Kotlin: for (item in list) with direct iteration, forEachIndexed for index+value. C#: foreach (var item in list) with IEnumerable. JavaScript: for (const item of list) with for-of, .forEach() method. Swift: for item in list with direct iteration. EK9: for item in list with direct iteration, works with List, Dict, and any iterable type, no break/continue.

branch collection condition control dict dictionary each element entry flow for in iterate iterator list loop migrate

for loop
for in
iterate collection
loop over list
for each

for fruit in fruits
        if fruit == "cherry"
          break
        stdout.println(`  ${fruit}`)

Explanation: EK9 has no break statement. For-in loops always process every element. Use stream pipelines with head to stop early. See ek9 -h E01070 for details.

for num in numbers
        total: total + num

for num in numbers
        if num == 0
          continue
        total: total + num

Explanation: EK9 has no continue statement. Use filter in a stream pipeline to skip unwanted items, or use an if block inside the loop body. See ek9 -h E01071 for details.


Question: How does the while loop work?

How do I write a condition-based loop in EK9?

How do I loop while a condition is true in EK9?

Answer:
EK9's while loop repeatedly executes a body as long as a Boolean condition is true. The condition is checked before each iteration, so the body may never execute if the condition is initially false.

BASIC WHILE
Loop while a condition holds:
  counter <- 0
  while counter < 5
    stdout.println($counter)
    counter: counter + 1
This prints 0, 1, 2, 3, 4. The condition 'counter < 5' is checked before each iteration.

ITERATOR PATTERN
A common use of while is consuming an iterator:
  iter <- items.iterator()
  while iter?
    stdout.println(iter.next())
The ? operator checks if the iterator is set (has more items). This continues until the iterator is exhausted.

COUNTER ACCUMULATION
Build up a result across iterations:
  sum <- 0
  n <- 1
  while n <= 100
    sum: sum + n
    n: n + 1
This computes the sum of 1 to 100.

CONDITION IS A BOOLEAN EXPRESSION
Any expression producing a Boolean works as the condition:
  while not finished and retries < maxRetries
    attempt()
    retries: retries + 1

WHILE VS FOR-RANGE
Use for-range when you know the number of iterations:
  for i in 1 ... 10
Use while when the number of iterations depends on a runtime condition:
  while hasMoreData()

WHILE VS FOR-IN
Use for-in to iterate over a collection directly:
  for item in items
Use while with an iterator when you need more control:
  while iter?

NO BREAK
There is no break statement to exit a while loop early. Use guards or restructure the condition instead. See Q144 for why break was removed and Q125 for alternatives.

While loops support guard variables (see Q74) and can return values as expressions (see Q81).

See Q61 for if statements. See Q64 for for-range loops. See Q65 for for-in loops. See Q67 for do-while loops. See Q89 for stream pipelines as loop alternatives. See Q125 for head as the only early exit mechanism. See Q287 for process-until-done patterns with while.

defines module qa.flow.condition.loop

  defines function

    supplyStart() as pure
      -> initial as Integer
      <- rtn as Integer: initial

    supplyBoolean() as pure
      -> initial as Boolean
      <- rtn as Boolean: initial

  defines program

    WhileDemo()
      stdout <- Stdout()

      // === BASIC WHILE ===

      counter <- supplyStart(0)
      loopLimit <- 5
      while counter < loopLimit
        stdout.println(`Counter: ${counter}`)
        counter: counter + 1

      // === SUM ACCUMULATION ===

      sum <- 0
      n <- supplyStart(1)
      maxIterations <- 10
      while n <= maxIterations
        sum: sum + n
        n: n + 1
      stdout.println(`Sum 1..10: ${sum}`)

      // === ITERATOR PATTERN ===

      names <- ["Alice", "Bob", "Charlie"]
      iter <- names.iterator()
      stdout.println("Names via iterator:")
      while iter?
        stdout.println(`  ${iter.next()}`)

      // === COUNTDOWN ===

      remaining <- supplyStart(3)
      while remaining > 0
        stdout.println(`${remaining}...`)
        remaining: remaining - 1
      stdout.println("Done!")

      // === COMPOUND CONDITION ===

      attempts <- 0
      maxAttempts <- 5
      succeeded <- supplyBoolean(false)
      maxRetries <- 3
      while not succeeded and attempts < maxAttempts
        attempts: attempts + 1
        if attempts == maxRetries
          succeeded: true
      stdout.println(`Succeeded after ${attempts} attempts`)

Java: while (condition) { } with parentheses and braces required. Python: while condition: with colon and indentation, break/continue available. Rust: while condition { } with braces, loop for infinite loops, break/continue available. Go: for condition { } uses for keyword for while loops, no while keyword. C/C++: while (condition) { } with parentheses and braces. Kotlin: while (condition) { } with parentheses and braces, break/continue available. C#: while (condition) { } with parentheses and braces. JavaScript: while (condition) { } with parentheses and braces. Swift: while condition { } no parentheses, braces required. EK9: while condition with indentation, no parentheses, no braces, no break/continue, guard variables for safe assignment-and-check.

basic boolean branch condition control counter flow iterate iterator loop repeat simple while

while true
        stdout.println(`Counter: ${counter}`)
        counter: counter + 1
        if counter >= loopLimit
          break

Explanation: EK9 has no break statement. Structure the loop condition to control termination rather than using an infinite loop with break. See ek9 -h E07390 for details.


Question: How does do-while work in EK9?

How do I write a loop that runs at least once in EK9?

How do I loop with the condition at the end in EK9?

Answer:
EK9's do-while loop executes the body first, then checks the condition. This guarantees the body runs at least once, unlike a regular while loop that may not execute at all.

BASIC DO-WHILE
The body executes before the condition is checked:
  counter <- 0
  do
    stdout.println($counter)
    counter: counter + 1
  while counter < 5
This prints 0, 1, 2, 3, 4. The body executes first, then 'counter < 5' is checked.

AT-LEAST-ONCE GUARANTEE
Even if the condition is false from the start, the body still runs once:
  threshold <- 100
  do
    stdout.println("Runs once")
  while threshold < 10
The body prints 'Runs once' even though 100 is not less than 10.

WHEN TO USE DO-WHILE
Use do-while when you need at least one execution before checking the condition:
- Reading user input (try once, then check if valid)
- Processing a batch (do one iteration, check if more)
- Retry logic (attempt once, check if succeeded)

DO-WHILE VS WHILE
while: Checks BEFORE each iteration. Body may never execute.
do-while: Checks AFTER each iteration. Body always runs at least once.

  // while: might not run
  while hasData()
    process()

  // do-while: always runs once
  do
    process()
  while hasMoreData()

NO BREAK
Like all loops in EK9, do-while has no break statement. Structure your condition to control when the loop ends. See Q144 for why break was removed.

Do-while supports guard variables (see Q74) and can return values as an expression (see Q81).

See Q66 for while loops. See Q64 for for-range loops. See Q65 for for-in loops. See Q89 for stream pipelines as loop alternatives. See Q125 for head as the only early exit mechanism.

defines module qa.flow.postcondition.loop

  defines function

    supplyStart() as pure
      -> initial as Integer
      <- rtn as Integer: initial

  defines program

    DoWhileDemo()
      stdout <- Stdout()

      // === BASIC DO-WHILE ===

      counter <- supplyStart(0)
      loopLimit <- 5
      do
        stdout.println(`Counter: ${counter}`)
        counter: counter + 1
      while counter < loopLimit

      // === AT-LEAST-ONCE GUARANTEE ===

      // Even though 100 > 10, body runs once
      threshold <- supplyStart(100)
      ran <- false
      maxCount <- 10
      do
        ran: true
      while threshold < maxCount
      stdout.println(`Ran at least once: ${ran}`)

      // === ACCUMULATION ===

      sum <- 0
      n <- supplyStart(1)
      maxIterations <- 10
      do
        sum: sum + n
        n: n + 1
      while n <= maxIterations
      stdout.println(`Sum 1..10: ${sum}`)

      // === BUILDING A STRING ===

      built <- String()
      i <- supplyStart(1)
      countdown <- 5
      do
        if length built > 0
          built: built + ", "
        built: built + $i
        i: i + 1
      while i <= countdown
      stdout.println(`Built: ${built}`)

Java: do { } while (condition); with braces, parentheses, and semicolon required. C/C++: do { } while (condition); same syntax as Java. Python: no do-while construct, use while True with break instead. Rust: no do-while, use loop { if !condition { break } } instead. Go: no do-while, use for { if !condition { break } } instead. Kotlin: do { } while (condition) similar to Java but no semicolon. C#: do { } while (condition); same as Java. JavaScript: do { } while (condition); same as Java. Swift: repeat { } while condition with 'repeat' keyword instead of 'do'. EK9: do ... while condition with indentation, no braces, no parentheses, no semicolon, no break available, guard variables supported.

atleast body branch condition control do first flow loop once post repeat while

do
        stdout.println(`Counter: ${counter}`)
        counter: counter + 1
        if counter >= loopLimit
          break
      while true

Explanation: EK9 has no break statement. Place the real termination condition in the while clause rather than using break inside the body. See ek9 -h E07390 for details.


Question: Can switch return a value (switch as expression)?

How do I use switch as an expression in EK9?

How do I assign a value from a switch statement?

Answer:
Yes. EK9's switch can return a value by using the expression form. You declare a return variable inside the switch, each case assigns to it, and the whole switch becomes an expression that can be assigned to a variable.

BASIC SWITCH EXPRESSION
Assign the result of a switch:
  dayName <- switch dayNumber
    <- rtn as String?
    case 1
      rtn: "Monday"
    case 2
      rtn: "Tuesday"
    default
      rtn: "unknown"
The outer '<- dayName' captures the switch result. The inner '<- rtn as String?' declares the return variable. Each case assigns to 'rtn', and its final value is returned.

RETURN VARIABLE DECLARATION
The return variable appears right after the switch line, indented:
  result <- switch controlValue
    <- rtn as String?
    case ...
The '<- rtn as String?' declares a variable named 'rtn' of type String, initially unset. You can also initialise it: '<- rtn <- String()' or '<- rtn as String: "default"'.

NO RETURN STATEMENT
EK9 has no 'return' keyword. The value is returned implicitly through the declared return variable. Every path (all cases plus default) should set the variable.

BOOLEAN SWITCH AS TERNARY
A Boolean switch expression works as a ternary alternative:
  label <- switch isEnabled
    <- rtn as String?
    case true
      rtn: "ON"
    default
      rtn: "OFF"
This replaces the ternary operator from other languages.

EK9 also provides a dedicated ternary syntax for simple cases:
  label <- isEnabled <- "ON" else "OFF"

STATEMENT VS EXPRESSION
The statement form assigns to pre-declared variables (see Q63). The expression form creates a new variable from the result. Use expression form when the switch's purpose is to compute a single value.

Switch expressions work with all the same features: multiple case values (see Q69), comparison operators (see Q70), pattern matching (see Q71), given/when keywords (see Q72), and exhaustive enum matching (see Q73).

See Q63 for basic switch statement form. See Q61 for if/else as an alternative.

defines module qa.flow.switch.expression

  defines function

    supplyDay() as pure
      -> dayInput as Integer
      <- rtn as Integer: dayInput

    supplyEnabled() as pure
      -> enabled as Boolean
      <- rtn as Boolean: enabled

    supplyCommand() as pure
      -> cmd as String
      <- rtn as String: cmd

    supplyPriority() as pure
      -> level as Integer
      <- rtn as Integer: level

  defines program

    SwitchExpressionDemo()
      stdout <- Stdout()

      // === BASIC SWITCH EXPRESSION ===

      dayNumber <- supplyDay(3)
      dayName <- switch dayNumber
        <- rtn as String?
        case 1
          rtn: "Monday"
        case 2
          rtn: "Tuesday"
        case 3
          rtn: "Wednesday"
        case 4
          rtn: "Thursday"
        case 5
          rtn: "Friday"
        default
          rtn: "weekend or invalid"
      stdout.println(`Day ${dayNumber}: ${dayName}`)

      // === BOOLEAN SWITCH AS TERNARY ALTERNATIVE ===

      isEnabled <- supplyEnabled(true)
      statusLabel <- switch isEnabled
        <- rtn as String?
        case true
          rtn: "ON"
        default
          rtn: "OFF"
      stdout.println(`Status: ${statusLabel}`)

      // === STRING SWITCH EXPRESSION ===

      command <- supplyCommand("start")
      response <- switch command
        <- rtn as String?
        case "start"
          rtn: "Starting service..."
        case "stop"
          rtn: "Stopping service..."
        case "restart"
          rtn: "Restarting service..."
        default
          rtn: "Unknown command"
      stdout.println(response)

      // === ASSIGNING EXPRESSION RESULT ===

      priority <- supplyPriority(2)
      urgencyLabel <- switch priority
        <- rtn as String?
        case 1
          rtn: "CRITICAL"
        case 2
          rtn: "HIGH"
        case 3
          rtn: "MEDIUM"
        default
          rtn: "LOW"
      stdout.println(`Priority ${priority}: ${urgencyLabel}`)

Java: switch expressions since Java 14 with arrow syntax and yield keyword. Rust: match is always an expression (returns a value). Kotlin: when is always an expression when exhaustive. Python: no switch expression (use dict mapping or if/else). Go: no switch expression form. C/C++: no switch expression (ternary for simple cases). C#: switch expressions since C# 8 with arrow syntax. JavaScript: no switch expression. Swift: no switch expression but if/switch can be used in return. EK9: switch expression with declared return variable, no return keyword, value returned implicitly through the variable, works with all switch features.

assign branch compute condition control error expression flow given guard ok result return switch ternary value

switch dayNumber
        case 1
          return "Monday"

Explanation: EK9 has no return statement. Switch expressions use a declared return variable (rtn) that is assigned in each case. The final value is returned implicitly. See ek9 -h E01072 for details.

case 1
          rtn: "Monday"
          break
        case 2
          rtn: "Tuesday"
          break

Explanation: EK9 has no break statement. Each switch case is self-contained with no fallthrough, making break unnecessary and nonexistent in the grammar. See ek9 -h E01070 for details.


Question: How do I match multiple values in one case?

How do I handle multiple case values in EK9?

How does EK9 replace switch fallthrough?

Can I list several values in a single case clause?

Answer:
EK9 uses comma-separated values in a single case clause. This replaces the fallthrough pattern found in Java, C, and JavaScript.

BASIC MULTI-VALUE CASE
List multiple values separated by commas:
  switch day
    case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
      dayType: "weekday"
    case "Saturday", "Sunday"
      dayType: "weekend"
    default
      dayType: "invalid"
If the value matches ANY of the listed values, the case body executes.

INTEGER MULTI-VALUE
Group numeric codes:
  switch errorCode
    case 400, 401, 403, 404
      category: "client error"
    case 500, 502, 503
      category: "server error"
    default
      category: "other"

MIXING LITERALS AND FUNCTION CALLS
Case values can include function calls alongside literals:
  switch temperature
    case currentTemperature("GB"), 21, 22, 23, 24
      comfort: "Perfect"
The function call is evaluated and compared just like the literals.

WHY NO FALLTHROUGH
Other languages use fallthrough for multi-value matching:
  // Java fallthrough pattern (error-prone)
  case 1:
  case 2:
  case 3:
    result = "low";
    break;  // forget this and bugs happen
EK9 eliminates this entirely. CERT ranks switch fallthrough as the #7 most dangerous coding error. Multiple values per case handles the same use case safely.

COMPLEXITY
Multiple values per case do NOT increase code complexity. 'case 25, 26, 27' is one code path, not three. The complexity is the same as a single-value case.

See Q63 for basic switch. See Q68 for switch as expression. See Q144 for why EK9 has no break/fallthrough.

defines module qa.flow.switch.multivalue

  defines program

    MultiCaseDemo()
      stdout <- Stdout()

      // === WEEKDAY/WEEKEND GROUPING ===

      days <- ["Monday", "Saturday", "Wednesday", "Sunday", "Friday"]
      for day in days
        dayType <- String()
        switch day
          case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
            dayType: "weekday"
          case "Saturday", "Sunday"
            dayType: "weekend"
          default
            dayType: "invalid"
        stdout.println(`${day} -> ${dayType}`)

      // === NUMERIC GROUPING ===

      codes <- [200, 301, 404, 500]
      for code in codes
        category <- String()
        switch code
          case 200, 201, 204
            category: "success"
          case 301, 302, 304
            category: "redirect"
          case 400, 401, 403, 404
            category: "client error"
          case 500, 502, 503
            category: "server error"
          default
            category: "other"
        stdout.println(`HTTP ${code} -> ${category}`)

      // === MULTI-VALUE IN EXPRESSION FORM ===

      season <- 7
      seasonName <- switch season
        <- rtn as String?
        case 3, 4, 5
          rtn: "Spring"
        case 6, 7, 8
          rtn: "Summer"
        case 9, 10, 11
          rtn: "Autumn"
        case 12, 1, 2
          rtn: "Winter"
        default
          rtn: "invalid month"
      stdout.println(`Month ${season} -> ${seasonName}`)

Java: fallthrough by default, must add break to prevent it, switch expressions use arrow syntax since Java 14. Python: case X | Y in match/case since 3.10, uses pipe operator. Rust: pattern1 | pattern2 in match arms, uses pipe operator. Go: case X, Y with comma-separated values and no fallthrough by default. C/C++: fallthrough by default, major bug source, CERT #7 most dangerous error. Kotlin: when with comma-separated values, no fallthrough. C#: no fallthrough, multiple labels with goto case (complex). JavaScript: fallthrough by default like Java. Swift: case X, Y with comma-separated values, no fallthrough. EK9: case X, Y with comma-separated values, no fallthrough exists, no break keyword.

branch break case comma condition control fallthrough flow group match migrate multiple several values

Explanation: EK9 requires a 'default' clause in switch statements to ensure all cases are handled. Omitting it triggers E07320. This prevents bugs where unexpected values silently fall through. See ek9 -h E07320 for details.

case 200, 201, 204
            category: "success"

case 200, 201, 204
            category: "success"
            break

Explanation: EK9 has no break statement — it does not exist in the language. Each case clause is self-contained with no fallthrough. The break keyword is deliberately excluded. See ek9 -h E01070 for details.


Question: How do I use comparison operators in switch cases?

Can I use less than or greater than in a case clause?

How do I match ranges in a switch statement?

How do comparison operators work in EK9 switch cases?

Answer:
EK9 switch cases can use comparison operators. Instead of matching exact values, you can write 'case < 5', 'case > 100', 'case >= threshold' and similar. The operator is applied to the switch control variable.

BASIC COMPARISON CASES
Use less-than and greater-than in cases:
  switch temperature
    case < 0
      label: "freezing"
    case < 15
      label: "cold"
    case < 25
      label: "comfortable"
    default
      label: "hot"
Order matters: the first matching case wins, just like if/else-if.

SUPPORTED OPERATORS
All comparison operators work in cases:
  case < 5       less than
  case > 100     greater than
  case <= 10     less than or equal
  case >= 90     greater than or equal
  case == 42     explicit equality
  case <> 0      not equal

EXPRESSIONS IN COMPARISONS
The right side of the operator can be any expression, including arithmetic and function calls:
  multiplier <- 5
  switch conditionValue
    case > 10 * multiplier
      label: "Very High"
    case < 12
      label: "Moderate"
    default
      label: "Normal"

MIXING WITH LITERALS
You can mix comparison cases with literal value cases in the same switch:
  switch score
    case < 0
      label: "invalid"
    case 0
      label: "zero"
    case > 100
      label: "over maximum"
    default
      label: "valid"

ORDER MATTERS
Cases are evaluated top to bottom. First match wins. Place more specific cases before general ones to avoid shadowing:
  switch temperature
    case < 0        checked first
    case < 15       checked second (only if not less than 0)
    default         everything else

Switch with comparisons works in expression form too (see Q68). Multiple values per case can be mixed with comparisons (see Q69).

See Q63 for basic switch. See Q68 for switch as expression. See Q71 for pattern matching in switch.

defines module qa.flow.switch.operators

  defines function

    supplyMultiplier()
      <- rtn <- 10

    supplyCheckValue()
      <- rtn <- 55

  defines program

    SwitchComparisonDemo()
      stdout <- Stdout()

      // === TEMPERATURE CLASSIFICATION ===

      temperatures <- [35, 15, -5, 22, 0]
      for temperature in temperatures
        classification <- String()
        switch temperature
          case < 0
            classification: "freezing"
          case < 10
            classification: "cold"
          case < 20
            classification: "cool"
          case < 30
            classification: "warm"
          default
            classification: "hot"
        stdout.println(`${temperature}C -> ${classification}`)

      // === GRADE CLASSIFICATION WITH EXPRESSION ===

      scores <- [95, 82, 71, 55, 40]
      for score in scores
        grade <- switch score
          <- rtn as String?
          case >= 90
            rtn: "A"
          case >= 80
            rtn: "B"
          case >= 70
            rtn: "C"
          case >= 60
            rtn: "D"
          default
            rtn: "F"
        stdout.println(`Score ${score} -> Grade ${grade}`)

      // === MIXING COMPARISONS WITH LITERALS ===

      testValues <- [-1, 0, 50, 100, 150]
      for testValue in testValues
        category <- String()
        switch testValue
          case < 0
            category: "negative"
          case 0
            category: "zero"
          case 100
            category: "perfect"
          case > 100
            category: "over limit"
          default
            category: "normal"
        stdout.println(`${testValue} -> ${category}`)

      // === EXPRESSIONS IN COMPARISONS ===

      multiplier <- supplyMultiplier()
      checkValue <- supplyCheckValue()
      assessment <- switch checkValue
        <- rtn as String?
        case > 10 * multiplier
          rtn: "exceeds scaled limit"
        case > multiplier
          rtn: "above base"
        default
          rtn: "within range"
      stdout.println(`${checkValue} -> ${assessment}`)

Java: no comparison operators in case labels, must use if/else-if chains. C#: relational patterns since C# 9 (case < 12 in switch expressions), closest to EK9. Rust: match guards with if (match x { n if n < 5 => ... }), separate from pattern. Python: no comparison in match/case, use if/elif. Go: no comparison in case, use if/else. Kotlin: when supports arbitrary boolean expressions (in 1..10, is Type), very flexible. C/C++: no comparison in case, constants only. JavaScript: no comparison in case labels. Swift: where clause for conditions (case let x where x < 5). EK9: comparison operators directly in case clause, mixed with literals and function calls, no separate guard syntax needed.

branch case comparison condition control flow greater inequality less operator range switch threshold

switch temperature
          case < 0
            classification: "freezing"
            break

Explanation: EK9 has no break statement. Switch cases with comparison operators are self-contained like all other case clauses. No fallthrough exists. See ek9 -h E01070 for details.


Question: How do pattern matching and regex work in switch?

Can I use regex in a switch case in EK9?

How do I match patterns in a switch statement?

Answer:
EK9 switch cases support pattern matching with the 'matches' and 'contains' operators. These work with String values.

REGEX MATCHING
Use 'case matches' with a regex literal:
  switch userInput
    case matches /^[A-Z]{2}[0-9]{4}$/
      category: "product code"
    case matches /^[0-9]+$/
      category: "numeric"
    default
      category: "text"
The regex literal uses /pattern/ syntax. The switch value is tested against the pattern.

SUBSTRING MATCHING
Use 'case contains' to check for substrings:
  switch logLine
    case contains "ERROR"
      severity: "high"
    case contains "WARN"
      severity: "medium"
    default
      severity: "low"

MIXING PATTERN TYPES
You can mix regex, contains, comparison operators, and literal values in the same switch:
  switch conditionVariable
    case 'D'
      label: "exact match"
    case matches /[nN]ame/
      label: "regex match"
    case > "Gandalf"
      label: "comparison match"
    default
      label: "default"
This is a powerful combination unique to EK9.

EXPRESSION FORM
Pattern matching works in switch expressions too:
  result <- switch text
    <- rtn as String?
    case matches /pattern/
      rtn: "matched"
    default
      rtn: "no match"

ORDER MATTERS
As with all switch cases, order matters. Place more specific patterns before general ones to avoid shadowing.

See Q33 for regex type details. See Q37 for String type details. See Q63 for basic switch. See Q70 for comparison operators in cases.

defines module qa.flow.switch.pattern

  defines program

    SwitchPatternDemo()
      stdout <- Stdout()

      // === REGEX MATCHING ===

      inputs <- ["AB1234", "hello", "42", "XY9999"]
      for userInput in inputs
        category <- switch userInput
          <- rtn as String?
          case matches /^[A-Z]{2}[0-9]{4}$/
            rtn: "product code"
          case matches /^[0-9]+$/
            rtn: "numeric"
          default
            rtn: "text"
        stdout.println(`"${userInput}" -> ${category}`)

      // === CONTAINS MATCHING ===

      logLines <- ["2024 ERROR disk full", "2024 WARN low memory", "2024 INFO started"]
      for logLine in logLines
        severity <- switch logLine
          <- rtn as String?
          case contains "ERROR"
            rtn: "HIGH"
          case contains "WARN"
            rtn: "MEDIUM"
          default
            rtn: "LOW"
        stdout.println(`${severity}: ${logLine}`)

      // === MIXED PATTERNS ===

      names <- ["Dave", "Name", "Zara", "Alice"]
      for nameEntry in names
        assessment <- switch nameEntry
          <- rtn as String?
          case matches /[nN]ame/
            rtn: "contains 'name'"
          case > "M"
            rtn: "second half of alphabet"
          case < "D"
            rtn: "early alphabet"
          default
            rtn: "mid alphabet"
        stdout.println(`${nameEntry} -> ${assessment}`)

Rust: match with pattern matching and destructuring, no inline regex, separate regex crate. Python: match/case since 3.10 with structural patterns, no regex in case clauses. Kotlin: when with arbitrary expressions, regex via matches() in guards. Java: no pattern matching in switch cases (instanceof patterns only since Java 21). Go: no pattern matching in switch. C/C++: no pattern matching in switch. JavaScript: no pattern matching in switch. Swift: pattern matching with case let and where clauses, no inline regex. Ruby: case/when with regex via === operator. EK9: case matches /regex/ and case contains "text" directly in case clauses, mixed with comparison operators and literals.

branch case condition contains control flow matches pattern regex regular string substring switch

case matches /^[A-Z]{2}[0-9]{4}$/
            rtn: "product code"
            break

Explanation: EK9 has no break statement. Switch cases with pattern matching are self-contained like all other cases. No fallthrough exists in the language. See ek9 -h E01070 for details.

case contains "ERROR"
            return "HIGH"

Explanation: EK9 has no return statement. Switch expressions use a declared return variable that is assigned in each case branch. See ek9 -h E01072 for details.


Question: What are given/when — alternative switch keywords?

Can I use given instead of switch in EK9?

What is the difference between switch/case and given/when?

Are given and when just synonyms for switch and case?

Answer:
Yes. EK9 provides 'given' as a synonym for 'switch', and 'when' as a synonym for 'case'. They are semantically identical — the choice is purely stylistic.

BASIC GIVEN/WHEN
Use given/when instead of switch/case:
  given direction
    when "north"
      y: y + 1
    when "south"
      y: y - 1
    default
      stdout.println("Unhandled")
This is exactly equivalent to using switch/case.

ALL FOUR COMBINATIONS
You can mix the keywords freely:
  switch x + case:     switch value case 1 ...
  switch x + when:     switch value when 1 ...
  given x + case:      given value case 1 ...
  given x + when:      given value when 1 ...
All four are valid and behave identically.

EXPRESSION FORM
Given/when works in expression form:
  outcome <- given isReady
    <- rtn as String?
    when true
      rtn: "proceed"
    default
      rtn: "wait"

WITH ALL FEATURES
All switch features work with given/when:
  given temperature
    when < 0
      label: "freezing"
    when 20, 21, 22
      label: "comfortable"
    default
      label: "other"
Comparison operators, multiple values, pattern matching, guards, and exhaustive enum matching all work identically.

WHEN IN IF STATEMENTS
The 'when' keyword is also a synonym for 'if':
  when temperature < 0
    stdout.println("freezing")
See Q61 for if/when usage.

STYLE PREFERENCE
Some developers prefer given/when because it reads more naturally for certain types of logic, especially with Boolean conditions. Others prefer switch/case for familiarity. EK9 supports both to accommodate different preferences.

See Q63 for basic switch/case. See Q68 for switch/given as expression. See Q61 for if/when statements.

defines module qa.flow.given.style

  defines function

    supplyStatusCode() as pure
      -> code as Integer
      <- rtn as Integer: code

    supplyReady() as pure
      -> ready as Boolean
      <- rtn as Boolean: ready

    supplyPriority() as pure
      -> level as Integer
      <- rtn as Integer: level

    supplyTemperature() as pure
      -> degrees as Integer
      <- rtn as Integer: degrees

    supplyDay() as pure
      -> day as String
      <- rtn as String: day

  defines program

    GivenWhenDemo()
      stdout <- Stdout()

      // === BASIC GIVEN/WHEN ===

      statusCode <- supplyStatusCode(200)
      statusMessage <- String()
      given statusCode
        when 200
          statusMessage: "OK"
        when 404
          statusMessage: "Not Found"
        when 500
          statusMessage: "Server Error"
        default
          statusMessage: "Unknown"
      stdout.println(`Status ${statusCode}: ${statusMessage}`)

      // === GIVEN/WHEN AS EXPRESSION ===

      isReady <- supplyReady(true)
      outcome <- given isReady
        <- rtn as String?
        when true
          rtn: "Proceed"
        default
          rtn: "Wait"
      stdout.println(`Ready: ${outcome}`)

      // === SWITCH WITH WHEN (MIXED) ===

      priority <- supplyPriority(1)
      urgency <- String()
      switch priority
        when 1
          urgency: "Critical"
        when 2
          urgency: "High"
        when 3
          urgency: "Medium"
        default
          urgency: "Low"
      stdout.println(`Priority ${priority}: ${urgency}`)

      // === GIVEN WITH COMPARISON OPERATORS ===

      temperature <- supplyTemperature(18)
      comfort <- String()
      given temperature
        when < 10
          comfort: "cold"
        when < 20
          comfort: "cool"
        when < 30
          comfort: "warm"
        default
          comfort: "hot"
      stdout.println(`${temperature}C is ${comfort}`)

      // === GIVEN/WHEN WITH MULTIPLE VALUES ===

      dayOfWeek <- supplyDay("Wednesday")
      scheduleType <- String()
      given dayOfWeek
        when "Monday", "Wednesday", "Friday"
          scheduleType: "gym day"
        when "Tuesday", "Thursday"
          scheduleType: "rest day"
        when "Saturday", "Sunday"
          scheduleType: "weekend"
        default
          scheduleType: "unknown"
      stdout.println(`${dayOfWeek} is ${scheduleType}`)

Kotlin: uses 'when' as its switch equivalent (not 'switch' keyword), very similar to EK9's given/when. Ruby: uses 'case/when' syntax, similar to EK9's alternative. Python: uses 'match/case' since 3.10. Rust: uses 'match' keyword. Go: uses 'switch/case'. Java: uses 'switch/case'. C/C++: uses 'switch/case'. JavaScript: uses 'switch/case'. Swift: uses 'switch/case'. EK9: provides both switch/case and given/when as interchangeable synonyms, plus 'when' as synonym for 'if'.

alternative branch case condition control flow given keyword preference style switch synonym when

given statusCode
        when 200
          statusMessage: "OK"
          break

Explanation: EK9 has no break statement. The given/when construct (synonym for switch/case) has no fallthrough, so break is unnecessary and nonexistent. See ek9 -h E01070 for details.

given isReady
        when true
          return "Proceed"
        default
          return "Wait"

Explanation: EK9 has no return statement. Given/when expressions use a declared return variable that is assigned in each branch. See ek9 -h E01072 for details.


Question: How does exhaustive enum switch work?

Does EK9 check that all enum values are handled in a switch?

How do I switch over an enumeration in EK9?

What happens if I miss an enum value in a switch?

Answer:
When you switch over an enumeration and use direct constant references in cases, EK9's compiler verifies that ALL enumeration values are covered. Missing a value is a compile-time error.

EXHAUSTIVE ENUM SWITCH
All enum values must appear in cases:
  switch heading
    case Direction.North
      label: "going north"
    case Direction.South
      label: "going south"
    case Direction.East
      label: "going east"
    case Direction.West
      label: "going west"
    default
      label: "direction not set"
The compiler checks that North, South, East, and West are ALL present. Missing one is a compile error.

DEFAULT IS REQUIRED
Even with all enum values listed, a 'default' block is required. This handles the case where the enum variable is unset (EK9's tri-state semantics mean a variable can be present but have no value).

COMPILE-TIME SAFETY
If you later add a new value to the enum (say Direction.NorthWest), the compiler will flag every exhaustive switch that is now incomplete. This forces you to handle the new value everywhere, preventing silent bugs.

NON-EXHAUSTIVE OPT-OUT
Use the equality operator '==' to disable exhaustive checking:
  switch heading
    case == Direction.North
      label: "going north"
    default
      label: "not north"
With '== Direction.North' instead of 'Direction.North', the compiler treats this as a comparison, not an exhaustive match. You can handle only the values you care about.

MULTIPLE ENUM VALUES PER CASE
Group enum values with commas:
  switch heading
    case Direction.North, Direction.South
      axis: "vertical"
    case Direction.East, Direction.West
      axis: "horizontal"
    default
      axis: "none"
This is exhaustive: all four values are covered across the two cases.

EXPRESSION FORM
Exhaustive enum switch works in expression form:
  label <- switch heading
    <- rtn as String?
    case Direction.North
      rtn: "N"
    ...
    default
      rtn: "?"

See Q63 for basic switch. See Q68 for switch expression form. See Q69 for multiple values per case. See Q99 for creating enumerations. See Q100 for how EK9 enums differ from Java enums. See Q226 for enum bug prevention.

defines module qa.flow.switch.exhaustive

  defines type

    Direction
      North,
      South,
      East,
      West

  defines function

    describeDirection() as pure
      -> heading as Direction
      <- description <- String()

      //Exhaustive: all four values must be present
      switch heading
        case Direction.North
          description: "heading north"
        case Direction.South
          description: "heading south"
        case Direction.East
          description: "heading east"
        case Direction.West
          description: "heading west"
        default
          description: "direction not set"

    describeAxis() as pure
      -> heading as Direction
      <- axis <- String()

      //Exhaustive with grouped values
      switch heading
        case Direction.North, Direction.South
          axis: "vertical"
        case Direction.East, Direction.West
          axis: "horizontal"
        default
          axis: "none"

    isNorth() as pure
      -> heading as Direction
      <- northward <- String()

      //Non-exhaustive: uses == operator to opt out
      switch heading
        case == Direction.North
          northward: "yes, heading north"
        default
          northward: "not heading north"

  defines program

    EnumSwitchDemo()
      stdout <- Stdout()

      // === EXHAUSTIVE SWITCH ===

      directions <- [Direction.North, Direction.South, Direction.East, Direction.West]
      for heading in directions
        stdout.println(describeDirection(heading))

      // === GROUPED ENUM VALUES ===

      for heading in directions
        stdout.println(`${describeDirection(heading)} is on ${describeAxis(heading)} axis`)

      // === NON-EXHAUSTIVE WITH == ===

      for heading in directions
        stdout.println(isNorth(heading))

      // === EXPRESSION FORM ===

      testHeading <- Direction.East
      symbol <- switch testHeading
        <- rtn as String?
        case Direction.North
          rtn: "N"
        case Direction.South
          rtn: "S"
        case Direction.East
          rtn: "E"
        case Direction.West
          rtn: "W"
        default
          rtn: "?"
      stdout.println(`Direction symbol: ${symbol}`)

Rust: match on enum is exhaustive, compiler error if arm missing, _ for wildcard. Kotlin: when on sealed class or enum is exhaustive when used as expression, else required. Java: switch on enum since Java 5, exhaustive checking only with switch expressions since Java 21 with sealed types. Python: match/case has no exhaustive checking for enums. Go: no enum type (iota constants), no exhaustive checking. C/C++: switch on enum has optional compiler warnings for missing cases (-Wswitch), not enforced. C#: switch on enum not exhaustive by default, Roslyn analyzers can check. Swift: switch on enum is exhaustive, compiler error if case missing, @unknown default for future values. EK9: switch on enum is exhaustive when using direct constant references, use == operator to opt out, default always required for unset handling.

branch check compiler complete condition control cover enum enumeration exhaustive flow migrate missing switch

switch heading
        case Direction.North
          description: "heading north"
          break

Explanation: EK9 has no break statement. Exhaustive enum switch cases are self-contained with no fallthrough. See ek9 -h E01070 for details.

describeDirection() as pure
      -> heading as Direction
      switch heading
        case Direction.North
          return "heading north"

Explanation: EK9 has no return statement. Functions use declared return variables. The compiler verifies all paths initialise the return variable. See ek9 -h E01072 for details.


Question: How do guard variables work in if statements?

How do I combine variable declaration with an if condition?

How does EK9 handle null-safe if checks?

How do I use the declaration operator in an if statement?

Answer:
Guard variables let you combine variable creation with an isSet check in a single if statement. The guard ensures the if body only executes when the value is meaningful.

BASIC GUARD PATTERN
Use the declaration operator (<-) to create a guard variable:
  if value <- getResult()
    stdout.println(value)
The variable 'value' is created and checked. If getResult() returns an unset value, the if body is skipped entirely. No exception, no crash.

GUARD WITH ADDITIONAL CONDITION
Use 'with' or 'then' to add a second condition after the guard:
  if value <- getResult() with value > 10
    stdout.println(value)
Both must be true: the guard must be SET and the extra condition must pass. Short-circuit applies: if the guard is unset, the condition is never evaluated.

IF/ELSE WITH GUARD
The else branch executes when the guard is unset:
  if value <- getResult()
    process(value)
  else
    handleMissing()

THREE GUARD OPERATORS
EK9 supports three operators in if guards, each with different semantics:
  1. Declaration (<-): Creates NEW variable, checks isSet of result
     if value <- getResult()
  2. Assignment (:=): Assigns to EXISTING variable, NO isSet check (blind)
     if existing := getResult() with existing > 0
  3. Guarded assignment (?=): Assigns to EXISTING variable, checks isSet of RHS
     if existing ?= getResult()
The declaration guard (<-) is by far the most common and recommended form.

SCOPING
Guard variables created with <- are scoped to the if/else block. They do not leak into the surrounding scope, preventing accidental reuse of one-time values.

WHAT 'SET' MEANS
The guard calls the type's isSet (?) operator. Each type defines what 'set' means: Integer is set when it has a numeric value, String when it has text content, Optional when it contains a value, collections when they have been properly initialized.

See Q29 for unset variables. See Q61 for basic if/else without guards. See Q75 for guards in switch. See Q76 for guards in for loops. See Q77 for guards in while loops. See Q78 for guards in try blocks. See Q166 for consistent safe pattern. See Q269 for input validation with guards. See Q272 for defense in depth with guards. See Q285 for multiple precondition validation with guards.

defines module qa.flow.guard.ifstatement

  defines record

    Config
      host <- String()
      port <- Integer()

      default operator ?

  defines function

    getConfig()
      <- rtn <- Config()

    getActiveConfig()
      <- rtn <- Config()
      rtn.host: "localhost"
      rtn.port: 8080

  defines program

    GuardIfDemo()
      stdout <- Stdout()

      // === BASIC GUARD: if value <- expr() ===

      if config <- getActiveConfig()
        stdout.println("Got config: " + config.host)
      else
        stdout.println("No config available")

      // === GUARD WITH ADDITIONAL CONDITION ===

      if config <- getActiveConfig() with config.port > 0
        stdout.println("Config port: " + $config.port)

      // === GUARD WITH UNSET VALUE ===

      if config <- getConfig()
        stdout.println("This should not print")
      else
        stdout.println("Config was unset, handled safely")

      // === ASSIGNMENT GUARD (no isSet check) ===

      existing <- Config()
      if existing := getActiveConfig() with existing.port > 0
        stdout.println("Assigned: " + existing.host)

      // === WHEN KEYWORD (alternative to if) ===

      when config <- getActiveConfig()
        stdout.println("When guard works too: " + config.host)

Java: No guard syntax. Must write: 'var v = getValue(); if (v != null) { use(v); }' as two separate steps. Variable leaks into outer scope. Optional requires: 'getValue().ifPresent(v -> use(v))' which forces a lambda. Python: No guard syntax. Walrus operator (:=) in 3.8+ comes close: 'if (v := getValue()) is not None:' but only handles None, not general isSet semantics. Rust: 'if let Some(v) = get_value()' for Option destructuring. Close to EK9 guards but limited to pattern matching. Go: 'if v := getValue(); v != nil' allows init statement in if but uses nil checks, no isSet concept. Kotlin: 'val v = getValue(); if (v != null)' with smart cast. No single-expression guard. Swift: 'if let v = getValue()' for optional binding, closest to EK9 but limited to Optional type. EK9: 'if value <- getResult()' combines declaration, assignment, and isSet check in one expression. Works with any type that has the ? operator, not just Optional. Three operator variants for different semantics.

branch check condition control declaration flow guard if isset null null-safe operator safe scope unset

config <- getActiveConfig()
      if not config?
        return
      stdout.println("Got config: " + config.host)

Explanation: EK9 has no return statement. Guard variables in if statements combine declaration and isSet checking in one expression, replacing the early-return-if-null pattern. See ek9 -h E01072 for details.


Question: How do guard variables work in switch statements?

Can I guard a switch with a variable declaration?

What happens if a switch guard is unset?

Answer:
Guard variables in switch combine variable creation with an isSet check before the switch body executes. If the guard is unset, the entire switch (including default) is skipped.

BASIC SWITCH GUARD
Use the declaration operator (<-) with 'then' or 'with' to link the guard to the switch control:
  switch value <- getResult() then value
    case 1
      stdout.println("One")
    case 2
      stdout.println("Two")
    default
      stdout.println("Other")
If getResult() is unset, nothing executes. No exception.

SWITCH EXPRESSION WITH GUARD
Switch expressions return a value. The guard protects the entire expression:
  label <- switch priority <- getPriority() then priority
    <- rtn as String: "Unknown"
    case 1
      rtn: "Low"
    case 2
      rtn: "Medium"
    case 3
      rtn: "High"
    default
      rtn: "Other"
If the guard is unset, the expression evaluates to the default return value ("Unknown").

KEYWORD INTERCHANGEABILITY
'with' and 'then' are synonyms. Use whichever reads better:
  switch value <- getResult() with value
  switch value <- getResult() then value
Both are identical in behavior.

GIVEN/WHEN ALTERNATIVE
EK9 allows 'given' as a synonym for 'switch':
  given value <- getResult() then value
    when 1
      stdout.println("One")
See Q72 for more on given/when.

See Q63 for basic switch without guards. See Q68 for switch as expression. See Q74 for guards in if statements. See Q76 for guards in for loops. See Q243 for coalescing operators (??, ?:, <?, >?) that provide another approach to handling unset values.

defines module qa.flow.guard.switchstatement

  defines function

    getPriority()
      <- rtn <- Integer()
      rtn: 2

    getUnsetPriority()
      <- rtn <- Integer()

  defines program

    GuardSwitchDemo()
      stdout <- Stdout()

      // === BASIC SWITCH GUARD ===

      switch priority <- getPriority() then priority
        case 1
          stdout.println("Low priority")
        case 2
          stdout.println("Medium priority")
        case 3
          stdout.println("High priority")
        default
          stdout.println("Unknown priority")

      // === SWITCH EXPRESSION WITH GUARD ===

      label <- switch priority <- getPriority() then priority
        <- rtn as String: "Unknown"
        case 1
          rtn: "Low"
        case 2
          rtn: "Medium"
        case 3
          rtn: "High"
        default
          rtn: "Other"
      stdout.println("Priority label: " + label)

      // === GUARD WITH UNSET VALUE (entire switch skipped) ===

      switch priority <- getUnsetPriority() then priority
        case 1
          stdout.println("This should not print")
        default
          stdout.println("Default should not print either")

      stdout.println("After guarded switch with unset value")

Java: Java 21+ pattern matching in switch cannot combine guard declarations. Must write: 'var v = getValue(); switch (v) { ... }' as two steps. Variable leaks into outer scope. Kotlin: 'when' blocks cannot combine guard declaration with control expression. Must nest: 'val v = expr; when (v) { ... }'. Swift: 'switch expr' cannot combine optional binding with switch control. Must nest: 'if let v = expr { switch v { ... } }'. Go: No guard declarations in switch. Must declare and check separately. EK9: 'switch value <- getResult() then value' combines declaration, isSet check, and switch control in one line. Guards scope the variable to the switch block.

branch case condition control declaration expression flow given guard isset null-safe safe scope switch unset

priority <- getPriority()
      if not priority?
        return
      switch priority
        case 1
          stdout.println("Low priority")
          break

Explanation: EK9 has no return or break statements. Guard variables in switch combine declaration and isSet checking. Each case is self-contained with no fallthrough. See ek9 -h E01072 for details.

switch priority <- getPriority() then priority
        case 1
          return "Low"

Explanation: EK9 has no return statement. Switch expressions use a declared return variable. The value is returned implicitly through the variable. See ek9 -h E01072 for details.


Question: How do guard variables work in for loops?

Can I guard a for loop with a variable declaration?

How does the for loop guard work in EK9?

What happens if a for loop guard is unset?

Answer:
In EK9, guard variables in for loops combine variable creation with an isSet check before the loop begins. The guard executes ONCE as a preamble, not on each iteration.

BASIC FOR-IN GUARD
Use the declaration operator (<-) with 'with' to link the guard to the loop:
  for config <- getConfig() with item in items
    process(config, item)
If getConfig() is unset, the entire loop is skipped. No exception.

FOR-RANGE GUARD
Guards also work with range-based for loops:
  for config <- getConfig() with i in 1 ... 10
    stdout.println(config.host + ": iteration " + $i)

GUARD TIMING
The guard evaluates ONCE before iteration begins. This is different from while loop guards that re-evaluate on each iteration. The guard sets up a precondition for the entire loop.

FOR EXPRESSION WITH GUARD
For loops can be expressions that return a value:
  result <- for config <- getConfig() with i in 1 ... 3
    <- rtn as String: "no config"
    rtn: config.host + " " + $i
If the guard is unset, the expression evaluates to the default return value.

WITHOUT A GUARD
The standard for-in loop needs no guard when iterating a collection:
  for item in items
    process(item)
Guards add safety when a precondition must be met before iterating.

See Q64 for for-range loops. See Q65 for for-in loops. See Q74 for guards in if statements. See Q77 for guards in while loops (with re-evaluation).

defines module qa.flow.guard.forloop

  defines record

    ServerConfig
      host <- String()
      port <- Integer()

      default operator ?

  defines function

    getServerConfig()
      <- rtn <- ServerConfig()
      rtn.host: "api.example.com"
      rtn.port: 443

    getNoConfig()
      <- rtn <- ServerConfig()

  defines program

    GuardForDemo()
      stdout <- Stdout()

      items <- ["alpha", "beta", "gamma"]

      // === FOR-IN WITH GUARD ===

      for config <- getServerConfig() with item in items
        stdout.println(`${config.host} processing: ${item}`)

      // === FOR-RANGE WITH GUARD ===

      for config <- getServerConfig() with i in 1 ... 3
        stdout.println(`Iteration ${i} on ${config.host}`)

      // === GUARD WITH UNSET VALUE (loop skipped) ===

      for config <- getNoConfig() with item in items
        stdout.println("This should not print: " + item)

      stdout.println("After guarded for with unset value")

Java: No guard concept in for loops. Must check preconditions separately: 'var c = getConfig(); if (c != null) { for (item : items) { ... } }'. Python: No guard in for loops. Must wrap: 'config = get_config(); if config: for item in items: ...'. Rust: No guard in for loops. Must wrap: 'if let Some(c) = get_config() { for item in items { ... } }'. Go: No guard in for loops. Must check nil separately. EK9: 'for config <- getConfig() with item in items' combines precondition check and iteration in one statement. Guard evaluates once before loop begins.

branch condition control declaration flow for guard isset iteration loop null-safe preamble precondition range safe

config <- getServerConfig()
      if not config?
        return
      for item in items
        stdout.println(`${config.host} processing: ${item}`)

Explanation: EK9 has no return statement. Guard variables in for loops combine the precondition check and iteration in one statement, replacing the check-then-return pattern. See ek9 -h E01072 for details.

for config <- getServerConfig() with i in 1 ... 3
        stdout.println(`Iteration ${$i} on ${config.host}`)
        if i == 2
          break

Explanation: EK9 has no break statement. For loops with guards always iterate through the full range. Use stream pipelines with head if you need to stop early. See ek9 -h E01070 for details.


Question: How do guard variables work in while loops?

Can I guard a while loop with a variable declaration?

How does the while loop guard work in EK9?

What is the difference between while guards and for guards?

Answer:
In EK9, guard variables in while loops combine variable creation with an isSet check. Unlike for loop guards that evaluate once, while loop guards are re-evaluated on EVERY iteration.

BASIC WHILE GUARD
Use the declaration operator (<-) with 'with' to link the guard to the loop condition:
  while value <- getNextItem() with value > 0
    process(value)
Both checks happen each iteration: first the guard (isSet), then the condition (> 0). If either fails, the loop terminates.

GUARD RE-EVALUATION
This is the key difference from for loop guards. In a while loop, the guard expression is called again each time the loop condition is checked. This makes while guards perfect for consuming iterators or polling:
  while item <- source.next() with item?
    process(item)

GUARD TERMINATES LOOP
If the guard becomes unset on any iteration, the loop terminates cleanly. No exception is thrown. This is a safe alternative to checking for null in a while condition.

WHILE EXPRESSION WITH GUARD
While loops can be expressions that return a value:
  result <- while value <- getNext() with value > 0
    <- rtn as String: "none found"
    rtn: $value
If the guard is unset on the first check, the expression evaluates to the default return value.

THREE GUARD OPERATORS IN WHILE
All three guard operators work in while loops:
  1. Declaration (<-): Creates variable, checks isSet each iteration
     while value <- getNext() with condition
  2. Assignment (:=): Assigns each iteration, NO isSet check
     while existing := getNext() with existing > 0
  3. Guarded assignment (?=): Assigns only if RHS is SET
     while existing ?= getNext() with condition

See Q66 for basic while loops. See Q67 for do-while loops. See Q74 for guards in if. See Q76 for guards in for loops (one-time evaluation).

defines module qa.flow.guard.whileloop

  defines function

    supplyCounter()
      <- rtn <- Integer()
      rtn: 5

    supplyStart() as pure
      -> initial as Integer
      <- rtn as Integer: initial

  defines program

    GuardWhileDemo()
      stdout <- Stdout()

      // === BASIC WHILE GUARD ===

      counter <- supplyStart(3)
      while guardedCount <- supplyCounter() with counter > 0
        stdout.println(`Counter: ${counter} value: ${guardedCount}`)
        counter: counter - 1

      // === ASSIGNMENT GUARD (no isSet check) ===

      loops <- supplyStart(2)
      existing <- Integer()
      while existing := supplyCounter() with loops > 0
        stdout.println("Assigned value: " + $existing)
        loops: loops - 1

      stdout.println("While guard demo complete")

Java: No guard in while. Must write: 'T v; while ((v = getNext()) != null && v > 0) { ... }' mixing assignment and null check in condition. Error-prone. Python: No guard in while. Walrus operator helps: 'while (v := get_next()) is not None and v > 0:' but only handles None. Rust: 'while let Some(v) = get_next()' for iterator consumption. Close to EK9 but limited to pattern matching. Go: No guard in while (Go only has for). Must use: 'for v := getNext(); v != nil; v = getNext() { ... }'. EK9: 'while value <- getNext() with value > 0' combines declaration, isSet check, and loop condition. Guard re-evaluates each iteration, perfect for consuming sequences.

branch condition consume control declaration flow guard isset iteration loop null-safe poll reevaluate safe terminate while

while true
        guardedCount <- supplyCounter()
        if not guardedCount?
          break
        if counter <= 0
          break
        stdout.println(`Counter: ${$counter} value: ${$guardedCount}`)
        counter: counter - 1

Explanation: EK9 has no break statement. While loop guards combine declaration, isSet checking, and the loop condition, eliminating the need for break-based exit patterns. See ek9 -h E07390 for details.


Question: How do guard variables work in try blocks?

Can I guard a try block with a variable declaration?

How does exception handling work in EK9?

Answer:
In EK9, guard variables in try blocks combine variable creation with an isSet check before executing the try body. If the guard is unset, the try body is skipped, catch does NOT execute, but finally still runs.

BASIC TRY GUARD
Use the declaration operator (<-) to guard a try block:
  try resource <- acquireResource()
    useResource(resource)
  catch
    -> ex as Exception
    handleError(ex)
  finally
    cleanup()
If acquireResource() returns unset, the try body is skipped. The catch block does NOT fire because no exception occurred. The finally block ALWAYS runs.

GUARD VS EXCEPTION
This is the key distinction:
  Guard unset: Resource unavailable. Try body skipped. No error.
  Exception: Resource acquired but fails during use. Catch handles it.
Guard failures are expected situations (optional resource not available). Exceptions are unexpected errors during processing.

TRY EXPRESSION WITH GUARD
Try can be an expression that returns a value:
  result <- try config <- loadConfig()
    <- rtn as String: "default"
    rtn: config.host
  catch
    -> ex as Exception
    rtn: "error: " + $ex
If the guard is unset, the expression evaluates to the default return value.

FINALLY ALWAYS RUNS
Regardless of whether the guard is set or unset, or whether an exception occurs, the finally block always executes. This is identical to Java's finally guarantee.

See Q74 for guards in if. See Q75 for guards in switch. See Q76 for guards in for loops. See Q77 for guards in while loops. See Q134 for basic try/catch without guards. See Q135 for try/catch/finally. See Q137 for try-with-resources.

defines module qa.flow.guard.tryblock

  defines record

    DbConnection
      url <- String()

      default operator ?

  defines function

    openConnection()
      <- rtn <- DbConnection()
      rtn.url: "jdbc:example:db"

    openFailedConnection()
      <- rtn <- DbConnection()

  defines program

    GuardTryDemo()
      stdout <- Stdout()

      // === BASIC TRY GUARD (resource available) ===

      try conn <- openConnection()
        stdout.println("Connected to: " + conn.url)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)
      finally
        stdout.println("Cleanup after connection attempt")

      // === TRY GUARD WITH UNSET (try body skipped) ===

      try conn <- openFailedConnection()
        stdout.println("This should not print")
      catch
        -> ex as Exception
        stdout.println("Catch should not fire either")
      finally
        stdout.println("Finally always runs even when guard is unset")

      // === TRY EXPRESSION WITH GUARD ===

      result <- try conn <- openConnection()
        <- rtn as String: "no connection"
        rtn: "connected to " + conn.url
      catch
        -> ex as Exception
        rtn: "error"

      stdout.println("Result: " + result)

Java: No guard in try. Try-with-resources handles cleanup but cannot skip the try body if resource is unavailable. Must wrap: 'var r = acquire(); if (r != null) { try { use(r); } catch ... }'. Python: No guard in try. 'with' statement handles cleanup but cannot conditionally skip. Must wrap: 'r = acquire(); if r: try: use(r)'. Rust: No guard in try (Rust uses Result/Option instead of try/catch). Pattern matching with '?' operator is different. Go: No try/catch. Defers handle cleanup. Guard concept does not apply. EK9: 'try resource <- acquireResource()' combines resource acquisition, isSet check, and exception handling. Guard failure is silent (no exception), finally always runs.

acquire branch catch condition control declaration error exception finally flow guard handle isset migrate null-safe resource safe try

conn <- openConnection()
      if not conn?
        return
      try
        stdout.println("Connected to: " + conn.url)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

Explanation: EK9 has no return statement. Try guards combine resource acquisition and isSet checking. If the guard is unset, the try body is skipped safely without needing an early return. See ek9 -h E01072 for details.

try conn <- openConnection()
        return "connected to " + conn.url

Explanation: EK9 has no return statement. Try expressions use a declared return variable that is assigned in the body and catch branches. See ek9 -h E01072 for details.


Question: What is the guarded assignment operator :=? in EK9?

How do I assign only if a variable is unset?

How do I set a default value for an unset variable?

Answer:
The guarded assignment operator (:=?) assigns a value ONLY if the target variable is currently UNSET. If the variable already has a value, the assignment is silently skipped.

BASIC USAGE
Assign only when the variable has no value:
  name <- String()
  name :=? "Default Name"
  name :=? "Second Attempt"
After these lines, name is "Default Name". The second :=? is skipped because name is already set.

SETTING DEFAULTS
The most common use is providing fallback values:
  config <- loadFromFile()
  config :=? loadFromEnvironment()
  config :=? hardcodedDefault()
This creates a priority chain: file config wins, then environment, then hardcoded default. Only the first successful source is used.

DISTINCTION FROM OTHER OPERATORS
EK9 has three assignment-family operators with different guard semantics:
  :=  assigns unconditionally (blind assignment)
  :=? assigns only if TARGET is currently UNSET
  ?=  assigns only if SOURCE (RHS) is SET

The key difference: :=? checks the LEFT side (target). ?= checks the RIGHT side (source).

IN CONTROL FLOW
The :=? operator also works in control flow guards:
  if existing :=? getValue() with existing > 10
    process(existing)
Here, existing is only assigned if it was previously unset. The 'with' condition is then checked.

  try config :=? loadConfig()
    useConfig(config)
Only loads if config was unset. Useful for lazy initialization in try blocks.

LAZY INITIALIZATION PATTERN
Combine :=? with functions for lazy evaluation:
  cache <- String()
  cache :=? expensiveComputation()
The computation only runs if cache is unset. This is a clean lazy-init pattern without explicit if checks.

See Q22 for variable declaration. See Q29 for unset variables. See Q74 for guard variables in if. See Q78 for guards in try blocks. See Q104 for uninitialised properties in classes. See Q243 for coalescing operators (??, ?:) as alternatives to guarded assignment for handling unset values. See Q286 for first-wins accumulation with guarded assignment.

defines module qa.flow.guard.assignment

  defines function

    loadPrimary()
      <- rtn <- String()

    loadFallback()
      <- rtn <- String()
      rtn: "fallback-value"

    loadDefault()
      <- rtn <- String()
      rtn: "hardcoded-default"

  defines program

    GuardedAssignmentDemo()
      stdout <- Stdout()

      // === BASIC :=? USAGE ===

      name <- String()
      nameStatus <- "unset"
      if name?
        nameStatus: "set"
      stdout.println("Before: name is " + nameStatus)

      name :=? "Default Name"
      stdout.println("After first :=? name is: " + name)

      name :=? "Second Attempt"
      stdout.println("After second :=? name is still: " + name)

      // === PRIORITY CHAIN PATTERN ===

      config <- String()
      config :=? loadPrimary()
      config :=? loadFallback()
      config :=? loadDefault()
      stdout.println("Config resolved to: " + config)

      // === ALREADY SET VARIABLE ===

      greeting <- loadPrimary()
      greeting :=? "Goodbye"
      stdout.println("Greeting stayed: " + greeting)

Java: No direct equivalent. Must write: 'if (value == null) { value = getDefault(); }'. Verbose and error-prone. Optional.orElse() handles one level but not chains. Python: No direct equivalent. 'value = value or default' is common but broken for falsy values (0, empty string). 'if value is None: value = default' is correct but verbose. Rust: No direct equivalent. Option::get_or_insert() modifies in place but requires mut. Chaining defaults requires nested unwrap_or_else(). Go: No direct equivalent. 'if v == nil { v = getDefault() }' required. No compound operator. Kotlin: Elvis operator '?:' handles null: 'value = value ?: default'. Close but only for null, not general isSet. EK9: 'value :=? default' is a single operator that checks the target's isSet state. Works with any type, chains naturally for priority fallbacks.

assignment branch chain condition conditional control default fallback flow guarded initialize isset lazy null-safe operator priority safe unset

guarded assignment
conditional assignment

config <- loadPrimary()
      if config?
        return config
      config <- loadFallback()
      if config?
        return config
      config <- loadDefault()

Explanation: EK9 has no return statement. The guarded assignment operator :=? creates a clean priority chain pattern without needing early returns after each check. See ek9 -h E50050 for details.


Question: Can a for-range loop return a value (for-range as expression)?

How do I use a for-range loop as an expression in EK9?

How do I accumulate a result from a counting loop?

How does the for-range expression form work?

Answer:
Yes. A for-range loop can return a value by declaring a return variable inside the loop body. The return variable is updated across iterations and its final value becomes the expression result.

BASIC FOR-RANGE EXPRESSION
Accumulate a sum from 1 to 5:
  total <- for i in 1 ... 5
    <- rtn <- 0
    rtn: rtn + i
The outer '<- total' captures the loop result. The inner '<- rtn <- 0' declares the return variable initialised to 0. Each iteration adds to 'rtn'. After the loop completes, the final value of 'rtn' (15) is assigned to 'total'.

RETURN VARIABLE DECLARATION
The return variable appears right after the for-range line, indented:
  result <- for i in 1 ... limit
    <- rtn <- 0
    rtn: rtn + i
The '<- rtn <- 0' creates a variable named 'rtn' of type Integer (inferred from the literal 0), initialised to 0. You can also use typed declaration: '<- rtn as Integer: 0'.

BUILDING STRINGS
Concatenate values across iterations:
  countdown <- for i in 5 ... 1 by -1
    <- rtn <- ""
    if length rtn > 0
      rtn: rtn + ", "
    rtn: rtn + $i
Result: '5, 4, 3, 2, 1'.

WITH STEP
Expression form works with the 'by' keyword:
  sumEvens <- for i in 2 ... 10 by 2
    <- rtn <- 0
    rtn: rtn + i
Result: 30 (2+4+6+8+10).

STATEMENT VS EXPRESSION
The statement form (see Q64) executes code for side effects. The expression form computes and returns a value. Use expression form when the loop's purpose is to produce a single result.

EK9's for-range expression eliminates the need for external accumulator variables that are common in other languages. The return variable is scoped to the loop, making the intent clear.

See Q64 for for-range statement form. See Q68 for switch expression form. See Q81 for while/do-while expressions. See Q82 for for-in expression. See Q122 for stream collect as aggregation (alternative to loop accumulation). See Q237 for streams vs loops decision guide. See Q295 for single-letter loop counter conventions.

defines module qa.flow.range.expression

  defines program

    ForRangeExpressionDemo()
      stdout <- Stdout()

      // === BASIC ACCUMULATION ===

      total <- for i in 1 ... 5
        <- rtn <- 0
        rtn: rtn + i
      stdout.println(`Sum 1..5: ${total}`)

      // === BUILDING A STRING ===

      countdown <- for i in 5 ... 1 by -1
        <- rtn <- ""
        if length rtn > 0
          rtn: rtn + ", "
        rtn: rtn + $i
      stdout.println(`Countdown: ${countdown}`)

      // === SUM EVEN NUMBERS ===

      sumEvens <- for i in 2 ... 10 by 2
        <- rtn <- 0
        rtn: rtn + i
      stdout.println(`Sum evens 2..10: ${sumEvens}`)

      // === FACTORIAL USING RANGE ===

      factorial <- for i in 1 ... 6
        <- rtn <- 1
        rtn: rtn * i
      stdout.println(`6! = ${factorial}`)

Java: no for-loop expression form, must declare accumulator outside loop. Python: list comprehension or functools.reduce() for accumulation. Rust: iterators with .fold() or .sum() or .collect(), (1..=5).sum::<i32>(). Go: no loop expression, must use external accumulator. Kotlin: (1..5).fold(0) { acc, i -> acc + i } or sumOf for numeric. C#: Enumerable.Range(1, 5).Sum() or .Aggregate() for general fold. JavaScript: Array.from({length: 5}, (_, i) => i + 1).reduce((a, b) => a + b). Swift: (1...5).reduce(0, +) for sum. EK9: for-range expression with declared return variable, accumulator scoped to loop, no external variable needed, works with Integer, Float, Duration ranges.

accumulate branch compute condition control expression flow fold for range reduce sum value

total <- 0
      for i in 1 ... 5
        total: total + i
        if total > 10
          break

Explanation: EK9 has no break statement. For-range expression form scopes the accumulator inside the loop and always runs to completion. Use stream pipelines if early termination is needed. See ek9 -h E01070 for details.

factorial <- 1
      for i in 1 ... 6
        factorial: factorial * i
      return factorial

Explanation: EK9 has no return statement. For-range expressions return the final value of the declared return variable implicitly. See ek9 -h E01072 for details.