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 THAT RE-POINTS AND CHECKS

A ':=' pre-flow assigns the right-hand side to an existing variable (an unset value included) and checks nothing, so the check goes in the control: 'then reading? and looping'. If reading is unset, the loop body never runs and the expression yields the returning variable's initial value:

  reading as Integer?
  looping <- true
  total <- while reading := nextReading() then reading? and 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 pre-flow runs once at entry. A '<-' or ':=?' guard that leaves its variable unset skips the body; after a ':=' the control's own 'x?' does that.

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 guard operators.

Example

defines module qa.controlflow.guardwhileexpression

  defines function

    nextReading()
      <- rtn as Integer: 42

  defines program

    GuardWhileExpressionDemo()
      stdout <- Stdout()

      // while EXPRESSION that re-points 'reading' and checks it in the control
      reading as Integer?
      looping <- true
      total <- while reading := nextReading() then reading? and 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

E08050 — A body-skipping guard ('<-', ':=?') or a failed 'x?' control can skip the while body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as Integer?') there would be nothing to return, so the compiler rejects it. Give the return an initial value. See ek9 -h E08050 for details.

Incorrect:

        <- rtn as Integer?
        rtn: reading

Correct:

        <- rtn as Integer: 0
        rtn: rtn + reading
Other ways to ask this
  • Can a while loop that returns a value also use a guard variable?
  • How do the :=? guard and a re-pointing ':=' work in a while expression?
  • while loop as an expression with a guard operator
  • How do I combine an assign-if-unset guard 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 (<-, :=?) or an explicit 'x := f() then x?' in one header, with the returning variable guaranteeing a defined result even when the guard is unset.

Keywords: expression, isset, value, loop, assignment, then, while, control, flow, returning, :=?, guard