Control flow
Everything yields a value — including the loops, which is less strange than it sounds.
if yields a value
There is no ternary operator, because there is no need for one:
var n = 55
var label = if n % 2 == 0 then "even" else "odd"
print(n, "is", label)
55 is odd
Written across several lines it uses elif and reads as ordinary control flow. It is the same
expression either way — the value is simply unused when nothing wants it:
grade(score: int) -> string
if score >= 90 then "excellent"
elif score >= 70 then "fine"
elif score >= 50 then "scraped it"
else "no"
print(grade(91), grade(72), grade(50), grade(12))
excellent fine scraped it no
An if being used for its value needs an else. Without one the open branch has no value to
give, and the compiler says so rather than inventing a zero.
Arms match a literal, a |-separated set of alternatives, a range, or fall through to else:
size(n: int) -> string
n match
0 -> "empty"
1 | 2 | 3 -> "small"
4..99 -> "medium"
else "large"
print(size(0), size(2), size(55), size(1000))
empty small medium large
A match used for a value has to be exhaustive. That is what the else is doing above: int
has more values than the arms name, so without it there would be inputs with no answer. Where the
arms genuinely cover everything — the variants of an enum, say — no else is needed and adding one
is a mistake, because it is the thing that would stop the compiler telling you about a variant you
forgot when you add it later.
Loops
while and for are the ordinary ones. A range is written a..b for inclusive and a..<b for
exclusive, which are the same two operators used everywhere else in the language:
var sum = 0
for i in 1..10 do sum = sum + i
var doubled = 0
for i in 0..<5 do doubled += i * 2
print("sum:", sum, "doubled:", doubled)
sum: 55 doubled: 20
do puts the body on the same line. Indent it instead and the do goes away:
var countdown = 3
while countdown > 0
print("t minus", countdown)
countdown -= 1
print("go")
t minus 3
t minus 2
t minus 1
go
A do … while puts the test at the foot, for a body that has to run before anything is asked, and
a continue in one reaches that test — the reference has both.
A loop with nothing to test is written loop, which is what while true was always being used to
say:
var n = 0
loop
n += 1
if n >= 4 then break
print("stopped at", n)
stopped at 4
This is the part worth slowing down for, because it replaces a pattern that is otherwise written
with a flag variable. break carries a value out, and an else block — after the body, as in
Python — supplies the value when the loop finishes without breaking:
first_multiple(of: int, upto: int) -> int
for i in 1..upto
if i % of == 0 then break i
else -1
print(first_multiple(7, 20), first_multiple(23, 20))
7 -1
Read the else as “and if it ran out”. Every break value and the else value share one type,
which becomes the loop’s type. Without an else, finishing normally yields nothing — so a
value-carrying break needs one, and the compiler will tell you if it is missing.
A loop takes no else, because it has no normal completion to have one for. That has a
consequence worth knowing: a loop that nothing breaks out of never finishes, so it can stand as
the last thing a function owing a value does, with nothing after it to supply that value. while true cannot say this — a condition is an expression the compiler does not evaluate, so a loop
written that way looks like one that might finish and the code after it looks reachable.
A for walks a collection directly:
var primes = [2, 3, 5, 7, 11]
var total = 0
for p in primes do total += p
print("count:", primes.len, "total:", total)
count: 5 total: 28
Where the stepping is something a range cannot describe there is a three-clause for, whose ; is
the only place in the language that token appears; the reference has it.
Next: functions, which have been quietly appearing in every example so far.