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.
match picks an arm
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
When the body has to run before anything is asked, put the test at the foot with do … while:
digits(n: int) -> string
var rest = n
var s = ""
do
s = str(rest % 10) + s
rest /= 10
while rest > 0
s
print(digits(0), digits(4071))
0 4071
Written as a while, that prints nothing at all for 0, and the usual patch is a special case
above the loop. The test at the foot is the fix. The one-line form is do rest /= 10 while rest > 0.
continue in a do … while runs the test, which is why the loop is worth having rather than
writing loop with if !cond then break at the bottom: that shape has no test for a continue to
reach, so the first one added to it skips the exit and the loop never finishes.
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
Loops yield values too
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.
Iterating over things
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
And when the stepping is something a range cannot describe, the three-clause form is there:
var n = 0
for var i = 1; i < 100; i *= 2
n += 1
print("doublings under 100:", n)
doublings under 100: 7
The ; in that header is the only place in the language it appears. It is deliberately not a
statement terminator — a line ends a statement, and a token that could also end one would give the
language two answers to the same question.
Next: functions, which have been quietly appearing in every example so far.