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 Containerand inside the container:
make -C examples/Choices runChoices
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 577Every 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 doEight 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 doNobody 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 52Then 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 elseA 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 9Two 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.