Does a guard in a while or do/while head run on every iteration?

← Control Flow · Ref: Q1983

No. The guard is the PRE-FLOW of the loop: it runs exactly ONCE, before the first iteration. After that only the condition after 'then' (or, for do/while, the condition after the closing 'while') is tested again.

  while reading := feed.next() then reading? and (passes < limit)
    passes++

feed.next() is called once. reading is assigned once (here 1) and stays 1 for every pass; the loop runs because the condition - 'reading?' and 'passes < limit' - is re-tested. Afterwards next() has been called exactly once.

AN UNSET VALUE SKIPS THE LOOP ENTIRELY

A do/while head has no condition to carry the check, so re-point first and wrap the loop:

  reading := drained.next()
  if reading?
    do
      skipped++
    while skipped < limit

drained.next() is unset, so reading is assigned the unset value (':=' always assigns) and the body never runs - not even the first pass a do/while would otherwise always make. A '<-' or ':=?' guard in the head ('do r <- next()') skips it the same way.

TO POLL UNTIL A VALUE IS UNSET

Put the fetch in the body and test the variable in the condition:

  arrival <- polled.next()
  while arrival?
    total += arrival
    arrival := polled.next()

That calls next() until it comes back unset (four calls for three readings).

The same rule holds for every pre-flow (<-, :=?, :=), in for, switch and try heads too. See Q1980 for the pre-flow operators, Q77 and Q1150 for while guards, Q1355 for do/while.

Example

defines module qa.controlflow.guardfamily.loops

  defines class

    Feed
      calls as Integer: 0
      available as Integer?

      default private Feed()

      Feed()
        -> readings as Integer
        available: readings

      next()
        <- rtn as Integer: Integer()
        calls++
        if calls <= available
          rtn: calls

      callCount()
        <- rtn as Integer: calls

      default operator ?

  defines program

    GuardsInLoops()
      stdout <- Stdout()
      limit <- 3

      //The guard is the pre-flow: it runs ONCE, before the loop; only 'then' is re-tested
      feed <- Feed(limit)
      reading <- Integer()
      passes <- 0
      while reading := feed.next() then reading? and (passes < limit)
        passes++
      stdout.println(`while re-point: passes=${passes} reading=${reading} next() calls=${feed.callCount()}`)

      //An unset value skips the loop entirely - even a do/while's first pass
      drained <- Feed(0)
      skipped <- 0
      reading := drained.next()
      if reading?
        do
          skipped++
        while skipped < limit
      stdout.println(`do/while re-point unset: body ran ${skipped} times, reading? ${reading?}`)

      //To POLL until a value is unset, fetch in the body and test the variable
      polled <- Feed(limit)
      total <- 0
      arrival <- polled.next()
      while arrival?
        total += arrival
        arrival := polled.next()
      stdout.println(`polling: total=${total} next() calls=${polled.callCount()}`)

Common mistakes

E50050 — 'reading' already exists, and '<-' always declares a new variable - EK9 does not shadow. Assign the existing variable with ':=' and check it in the condition. See ek9 -h E50050 for details.

Incorrect:

      while reading <- feed.next() then passes < limit

Correct:

      while reading := feed.next() then reading? and (passes < limit)
Other ways to ask this
  • Is 'while x := next() then x?' re-evaluating next() each time round the loop?
  • How many times is the function in a while guard called?
  • Does an unset guard skip the first pass of a do/while loop?
  • How do I loop until a function returns an unset value?

Coming from another language?

Java: 'while ((line = reader.readLine()) != null)' re-runs the assignment on every iteration - EK9's pre-flow does not; it is setup, not polling. Python: 'while (item := next_item()) is not None:' likewise re-evaluates each time. In EK9 write the poll explicitly: fetch before the loop, test 'x?' in the condition, fetch again at the end of the body.

Keywords: do, poll, once, pre-flow, :=, iteration, while, guard, skip, unset, loop