How do I use a guard in a for-range loop, including the expression form?

← Control Flow · Ref: Q1356

A for-range loop ('for i in start ... end') accepts a guard in its header, written BEFORE the loop variable and separated by 'with' (or 'then'). The for loop also has an expression form that yields a value via a returning variable.

FOR-RANGE EXPRESSION WITH A ?= GUARD

The guard comes first, then 'with', then the loop variable and range:

  step as Integer?
  total <- for step ?= getStep() with i in 1 ... 5
    <- rtn as Integer: 0
    rtn: rtn + (i * step)

If getStep() is unset the guarded assignment leaves 'step' unset, the loop body is skipped, and 'total' is the initial value 0.

FOR-RANGE STATEMENT WITH A :=? GUARD

  factor as Integer?
  sum <- 0
  for factor :=? getStep() with i in 1 ... 3
    sum: sum + (i * factor)

HEADER ORDER

The grammar is 'for <guard> with <var> in <range>'. The guard variable ('step', 'factor') is SEPARATE from the loop variable ('i'). Putting the guard after the range ('for i in 1 ... 5 with step ?= ...') is NOT valid.

BY STEP

A for-range may add 'by <step>' for non-unit or descending ranges; the guard still comes first: 'for g <- getG() with i in 10 ... 1 by -1'.

See Q64 for the plain for-range. See Q1149 for 'by'. See Q1357 for for-in guards. See Q1361 for guards across all expression-form constructs.

Example

defines module qa.controlflow.guardforrangeexpression

  defines function

    getStep()
      <- rtn as Integer: 2

  defines program

    GuardForRangeExpressionDemo()
      stdout <- Stdout()

      // for-range EXPRESSION with a ?= guard (guard FIRST, then 'with i in range')
      step as Integer?
      total <- for step ?= getStep() with i in 1 ... 5
        <- rtn as Integer: 0
        rtn: rtn + (i * step)
      stdout.println(`weighted total is ${total}`)

      // for-range STATEMENT with a :=? guard
      factor as Integer?
      sum <- 0
      for factor :=? getStep() with i in 1 ... 3
        sum: sum + (i * factor)
      stdout.println(`sum is ${sum}`)

Common mistakes

E02001 — The guard is the pre-flow and must come BEFORE the loop variable: 'for <guard> with <var> in <range>'. The guard variable is separate from the loop variable. See ek9 -h E02001.

Incorrect:

total <- for i in 1 ... 5 with step ?= getStep()
        <- rtn as Integer: 0
        rtn: rtn + (i * step)

Correct:

total <- for step ?= getStep() with i in 1 ... 5
        <- rtn as Integer: 0
        rtn: rtn + (i * step)
Other ways to ask this
  • Can a for i in 1 ... n loop use a guard variable?
  • for-range as an expression with a ?= guard
  • guarded assignment in a numeric for loop
  • How do I put a guard before the range in a for loop?

Coming from another language?

Java: a for loop is a statement and cannot bind a guarded, isSet-checked variable in its header nor yield a value. You would compute a nullable outside the loop and check it manually. Python/Kotlin: for-comprehensions/folds produce values but have no isSet-guard concept. EK9: the for-range header takes a guard (<-, ?=, :=?) before the loop variable, and its expression form yields the returning variable — with the guard able to skip the whole loop when unset.

Keywords: returning, with, isset, for-range, range, guarded, :=?, for, expression, loop, ?=, by, guard