How do I use a guard in the expression form of a while loop?
← Control Flow · Ref: Q1354
The while loop has an expression form (it yields a value via a returning variable) and that form accepts the same guards as every other control-flow construct. The guard is written in the header, BEFORE the loop control, separated by 'then' (or 'with').
WHILE EXPRESSION WITH A ?= GUARD
The guarded assignment (?=) assigns the right-hand side to the guard variable and checks whether it is SET. If unset, the whole loop body is skipped and the expression yields the returning variable's initial value:
reading as Integer? looping <- true total <- while reading ?= nextReading() then looping <- rtn as Integer: 0 rtn: rtn + reading looping: false // total is 0 if nextReading() was unset, otherwise the accumulated value
WHILE EXPRESSION WITH A :=? GUARD
Assign-if-unset (:=?) only assigns when the guard variable is currently unset; it still gates the body on the resulting set-state:
cached as Integer? loopOnce <- true lazy <- while cached :=? nextReading() then loopOnce <- rtn as Integer: -1 rtn: cached loopOnce: false
HEADER ORDER MATTERS
The guard comes FIRST, then the control condition after 'then' (or 'with'):
while <guard> then <control>
Not 'while <control> then <guard>'. The guard is evaluated once at entry; if it leaves the guard variable unset the body never runs.
RETURNING VARIABLE
The returning variable ('<- rtn as T: initial') is declared as the first line of the body and MUST have an initial value, so a skipped body still yields a defined result. This is why a guard-unset while expression returns the initialiser.
See Q77 for while guards in statement form. See Q81 for the while expression form without a guard. See Q1361 for the same guard used uniformly across all six expression-form constructs. See Q74 for the three guard operators.
Example
defines module qa.controlflow.guardwhileexpression defines function nextReading() <- rtn as Integer: 42 defines program GuardWhileExpressionDemo() stdout <- Stdout() // while EXPRESSION with a ?= guarded-assignment guard reading as Integer? looping <- true total <- while reading ?= nextReading() then looping <- rtn as Integer: 0 rtn: rtn + reading looping: false stdout.println(`accumulated total is ${total}`) // while EXPRESSION with a :=? assign-if-unset guard cached as Integer? loopOnce <- true lazy <- while cached :=? nextReading() then loopOnce <- rtn as Integer: -1 rtn: cached loopOnce: false stdout.println(`lazy value is ${lazy}`)
Common mistakes
E02001 — The guard is the pre-flow part and must come FIRST in the header; the loop control comes after 'then' (or 'with'). Writing the control first and the guard second is not valid. See ek9 -h E02001.
Incorrect:
total <- while looping then reading ?= nextReading() <- rtn as Integer: 0 rtn: rtn + reading
Correct:
total <- while reading ?= nextReading() then looping <- rtn as Integer: 0 rtn: rtn + reading looping: false
Other ways to ask this
- Can a while loop that returns a value also use a guard variable?
- How do ?= and :=? guards work in a while expression?
- while loop as an expression with a guard operator
- How do I combine a guarded assignment with a while loop that yields a result?
Coming from another language?
Java: no equivalent. A while loop is a statement, never an expression, and cannot declare a loop-scoped guarded variable. You would write a nullable variable, a manual isSet check, and a mutable accumulator across three or more statements. Kotlin: while is a statement; you would use a sequence/fold to get a value. Rust: 'while let Some(x) = iter.next()' binds and loops but is still a statement, not a value-producing expression. EK9: the while expression yields a value AND accepts a guard (<-, ?=, :=?) in one header, with the returning variable guaranteeing a defined result even when the guard is unset.
Keywords: flow, control, :=?, assignment, while, isset, loop, guard, value, expression, ?=, guarded, then, returning