Loops: while and each
On this page
Much of a program's work is doing something again and again. Cone has two loops: while, which repeats as long as a condition holds, and each, which does the counting for you. Here is while closing in on the digits of pi, a smaller fraction added on every pass:
mod pi;
import stdio use *;
fn main() {
var terms = 10; // how many fractions to add up
// The working values of the arcsine series for pi
var result = 0.5d;
var seed = 1d;
var top = 1d;
var bot = 1d;
var twos = 2d;
// The block runs again and again, as long as terms > 0
while terms > 0 {
top *= seed;
bot *= seed + 1d;
twos *= 2d * 2d;
result += top / (bot * (seed + 2d) * twos);
seed += 2d;
terms -= 1;
}
print <- "π = ", result * 6d, "\n";
}
It prints:
π = 3.14159
Try this: set terms to 2, and watch pi fall apart; then to 20, and watch the digits hold.
Repeating while a condition holds#
while takes a condition and a block. The condition is checked first, and if it holds the block runs; then it is checked again, and so on, until it fails. A condition that is false from the start runs the block no times at all. Something in the block has to change for the loop ever to end, here terms, which is why it is var.
The next one was written down in Alexandria a generation before Eratosthenes measured the Earth there. Euclid's way of finding the greatest common divisor of two numbers keeps replacing the pair with the smaller number and the remainder, until the remainder is 0:
var a = 84;
var b = 36;
while b != 0 {
let r = a % b; // r is fresh on every pass
a = b;
b = r;
}
print <- "gcd: ", a, "\n"; // gcd: 12
Some loops have no natural place to test at the top. Leave the condition out, and the loop runs until a break inside it says stop. The Collatz conjecture says that "halve it if it is even, otherwise triple it and add one" always comes down to 1 in the end. Nobody has proved it, and starting from 27 it wanders surprisingly far first:
var n = 27;
var steps = 0;
while {
break if n == 1;
n = if n % 2 == 0 { n / 2; } else { 3 * n + 1; };
steps += 1;
}
print <- "27 takes ", steps, " steps\n"; // 27 takes 111 steps
break if n == 1; is the same as if n == 1 { break; }, written the way it reads. break, continue and return, and only those three, can each take a condition after them like this, after the value if they give one: return x if x * x > limit;. A value that is itself an if is told apart by the block after its condition, so return if c { 1; } else { 2; }; returns a value, while return if c; returns when c holds.
Counting with each#
A great many loops just count, and writing the counter out by hand (start it, test it, step it) is where mistakes creep in. each does the counting. Give it a variable, the word in, and a range, and the block runs once for each number:
var f = 1;
each x in 2 .. 6 {
f *= x;
}
print <- "6! = ", f, "\n"; // 6! = 720
- 2 .. 6 runs through 6, including it.
- 0 ..< 10 stops before 10, which is handy for doing something ten times.
- by sets the step: 10 .. 0 by -2 counts down 10 8 6 4 2 0, and 0d .. 2d by 0.5d steps through 0 0.5 1 1.5 2.
A range never guesses its direction: 6 .. 2 on its own runs no passes, and counting down takes a negative step. The loop's variable is fresh on every pass and belongs to the loop; the block can read it but not change it. A loop that needs to steer its own count is a while.
A range is worked out once, before the first pass. Changing the end inside the body (0 ..< n while the body adds to n) does not move it, and a step held in a variable keeps the sign it had then. A step of zero runs no passes. A fractional step is not added up pass after pass, which would gather rounding errors: each pass's number is the start plus the count of steps taken times the step, so 0.0 .. 1.0 by 0.1 takes eleven passes, the last of them exactly 1.0. And a range stops as soon as a step would carry it past its end, or past what its type can hold: 250u8 .. 255 by 2 gives 250, 252 and 254, and never wraps round to 0.
Skipping ahead and leaving early#
continue abandons the rest of this pass and goes on with the next one; break leaves the loop at once. Both work the same in while and each:
var odd = 0;
each x in 0 ..< 10 {
continue if x % 2 == 0;
odd += x;
} // odd is 25: 1 + 3 + 5 + 7 + 9
When the test is about which numbers to visit at all, it can go straight after the range instead, and the numbers that fail it are skipped:
each x in 1 .. 50 if x % 7 == 0 {
print <- " ", x; // 7 14 21 28 35 42 49
}
A break or continue acts on the innermost loop around it. To reach an outer one, name it with a label, a name after a single quote, and use the same label on the break or continue. Here, the moment a divisor turns up, p is not prime, and continue 'next moves straight on to the next p, so only primes reach the print:
'next: each p in 2 .. 50 {
each d in 2 ..< p {
continue 'next if p % d == 0;
}
print <- " ", p; // 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
}
A loop that gives an answer#
Many loops are searches. They end in one of two ways: they find what they wanted, or they run out. Like a block, an if and a match, a loop can be a value, and both ways out are written plainly. break with a value leaves the loop and makes that its value; an else after the loop gives the value when it runs out without a break:
let first = each x in 1 .. 100 {
if x * x > 500 { break x; }
} else {
-1; // no square up to 100 would do
}; // first is 23
No flag variable, no "not found yet" placeholder. A while works the same way. Euclid knew that 6 is a perfect number, equal to the sum of its divisors (1 + 2 + 3), and also knew the next one:
var k = 7;
let perfect = while k < 1000 {
var sum = 0;
each d in 1 ..< k if k % d == 0 {
sum += d;
}
if sum == k { break k; }
k += 1;
} else {
0;
}; // perfect is 28
A while with no condition can end only at a break, so it takes no else: every break gives its value.
The loop's value has one type, whichever way it ends, so when the else gives a value, a break with none is refused. The else is an ordinary block that stands outside the loop: the loop's variables are out of sight there, a break in it belongs to a loop further out, and it may do other work before its value, or end in a return instead of giving one. A loop with an else can also be a plain statement, its else run for what it does when the loop runs out.
each walks more than numbers#
A range is only the first thing each can walk. A function can hand out values one at a time for it to walk, as the next page shows, and it walks the items of every kind of collection, which is where it really shines: Collections comes back to it.
You can now repeat work, stop it early, and get an answer back from a loop. Next come functions, which give a piece of work a name, so that pi can be worked out anywhere, to as many terms as you like.