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