This is part 4 of the series. Parts 1–3 gave you a running program, its fixed shape, and values whose type changes only where you can read it. This part is about flow: the two ways a program decides, and the three ways it repeats.

The claim worth reading the page for: the three loop forms are not three spellings of one thing. Each one states, in its first line, what is known before the loop runs — for knows the count, while knows only the condition, and repeat knows the question cannot even be asked until the body has run once. Choosing among them is not style. It is the loop admitting what it knows, in a form a reader can hold you to.

The example is examples/Choices in the tutorial repository, and its subject is one arithmetic walk: start at 27; halve an even number, triple an odd one and add one; repeat until the walk reaches 1. The rule is two lines, and the walk it makes wanders further than anyone expects — which gives every flow form something honest to do.

Getting the file

Same repository as the whole series:

git clone https://gitlab.com/mica-lang/mica-container.git
code mica-container      # then: Reopen in Container

and inside the container:

make -C examples/Choices run
Choices

The first eight steps from 27
  step 1   82  tripled, plus one
  step 2   41  halved
  step 3  124  tripled, plus one
  step 4   62  halved
  step 5   31  halved
  step 6   94  tripled, plus one
  step 7   47  halved
  step 8  142  tripled, plus one

The whole walk
  steps to 1  111
  highest     9232

Where the walk spent its steps
  ones       5
  tens       20
  hundreds   59
  thousands  27

The highest point, unwound
  9232 halves 4 times before it lands odd, on 577

Every number in that output is computed by the walk itself. None is written in the source, and the four tally lines sum to the step count — keep that in mind; one of the experiments below depends on it.

The example, walked

if: two roads, both written

if n mod 2 = 0 then
begin
    n := n / 2;
    WriteLn("  step %lld  %3lld  halved", k, n);
end
else
begin
    n := 3 * n + 1;
    WriteLn("  step %lld  %3lld  tripled, plus one", k, n);
end;

mod is the remainder, and / between integers stays an integer — you will see it truncate, deliberately and visibly, in the last experiment. Where more than one statement rides a branch, begin and end bracket the block; each branch here changes n and then says what it did, so every line of the opening table names the rule that produced it.

Note also what the condition is written with: = compares, because := assigns — part 2’s pair, now doing real work. And when you need not equal, the spelling is #, which you will meet in the while line below.

for: the count is known first

for k := 1 to 8 do

Eight steps, known before the loop starts — that is the whole reason for is the right form here. The counter k runs 1 to 8 and the loop ends, with no condition to maintain and none to get wrong. If the bounds cross — 1 to 0 — the loop runs zero times; a for admits its count may be nothing.

while: only the condition is known

while n # 1 do

Nobody knows in advance how many steps a walk takes — that is precisely what the program is computing — so a counted loop cannot be the spelling. while states the only fact available: keep going as long as n is not 1 (# is Mica’s not equal). Ask first, run after; if n is already 1, the body never runs. The walk from 27 takes 111 steps and touches 9232 on the way, and both numbers come out of this loop’s bookkeeping, not out of anyone’s foresight.

case: a value against named ranges

Inside the while, every visited number gets filed:

case n of
    1..9:
        ones := ones + 1;
    10..99:
        tens := tens + 1;
    100..999:
        hundreds := hundreds + 1
    else
        thousands := thousands + 1
end;

A case takes one value and dispatches it against labels — single values, lists, or ranges like 10..99 — with else catching everything unlabelled. Read the output’s tally again: 5 + 20 + 59 + 27 is 111, the step count. The program was written so its own output cross-checks itself, and the second experiment below shows that check earning its keep.

repeat: the body must run before the question makes sense

repeat
    n := n / 2;
    steps := steps + 1;
until n mod 2 = 1;

The last section unwinds the walk’s highest point, and it can promise something unusual: the body must run at least once. The highest number of any walk was just made by tripling an odd number, so it is even — a halving is guaranteed before is it odd yet? is worth asking. That is exactly the shape repeat states: run first, ask after. 9232 halves four times and lands on 577, odd, and the loop’s question finally answers yes.

Try it

1. Start somewhere else. Change 27 to 7 in both places and rebuild:

  steps to 1  16
  highest     52

Then try 1. The while answers steps to 1 0 — it asks before it runs, n is already 1, and the body never executes. A body-first loop given the same start would have stepped 1 → 4 → 2 → 1 and answered 3. Same subject, same rule, different answer — the difference between ask-first and run-first is not style.

2. Delete the else arm. Remove the else and the thousands line from the case and rebuild:

analyzer error 5458: the case over 'int64' has no else and its arms do not provably cover the domain: a case must say what happens to every value — add an else

A case must say what happens to every value of its selector — that is a rule of the language, checked before the program exists. Without it, the 27 four-digit values of the walk would have silently selected no arm, the tally would have summed to 84 instead of 111, and only the output’s self-check could have told you. The refusal makes that bug unwritable — and the self-checking tally stays worth having, because it guards the arithmetic no totality rule can see. Put the else back.

3. Seed the repeat with an odd number. Replace n := peak; before the repeat with n := 577; and rebuild:

  9232 halves 6 times before it lands odd, on 9

Two things happened. The body ran on a number that was already odd — run first, ask after, exactly as promised — and 577 halved to 288, integer division truncating in plain sight, then on down to 9. And the line still says 9232, because that label reads peak, which the seeding never touched. Part 3 taught this lesson with a printed label; here it is again: nothing keeps a label and a computation in step but you.

What the compiler proved

The format strings were checked against their arguments before the program existed, as in part 2. The case ranges were checked to be values of the selector’s type. And the output you saw is the same on x86-64 and ARM64 — the walk, the tally, and the truncating division all mean one thing.

And one promise the compiler holds outright: a case must say what happens to every value of its selector — cover the domain, or write the else that answers the rest. The second experiment showed the refusal that keeps it. What no totality rule can see is whether the arms do the right thing, which is why the self-checking tally is a design habit this series keeps returning to.

Next

Procedures and parameters — part 5: this same walk with its work named, and the proof that refactoring under the value rule changes nothing observable. (When you want for and case over richer domains — enumerations, characters, ranges — that story is Ordinals.) Every example behind the series lives in the tutorial repository, in learning order, each with a header that says what it teaches.