if, elif, else
The conditional statement looks like Python. An if line, a condition, a colon, and an indented body.
if score >= 90:
print("great")
elif score >= 50:
print("ok")
else:
print("try again")
Three rules:
- The condition must be a
Bool. A number likeif score:won't compile. Useif score != 0:orif Bool(score):instead. - There can be any number of
elifbranches, including none.elseis optional. elifandelsemust be indented at the same column as theirif. The compiler checks this.
A complete example:
def grade(score: Int32) -> NoneType
if score >= 90:
print("great")
elif score >= 50:
print("ok")
else:
print("try again")
return
def main() -> Int32
grade(95)
grade(60)
grade(20)
return 0
Variables and branches
If a variable is declared inside a branch and you want to use it afterwards, declare it in every branch. The compiler merges the values. This rule is explained with a working example on the Variables page.
Division by zero at compile time
When the compiler can see that you're dividing by the constant zero, it rejects the code before running it:
x: Int32 = 1 / 0 # compile error
This applies to /, //, and %. If the divisor comes from somewhere the compiler can't check, like user input, the check happens at runtime instead, and only in debug mode. Without debug mode it's undefined behavior.
while
while repeats a block as long as its condition stays true. The condition must be a Bool, same as an if.
def main() -> Int32
n: Int32 = 0
while n < 3:
print(n)
n += 1
return 0
Two keywords steer a loop from inside its body. break leaves the nearest loop immediately; continue skips the rest of the body and starts the next iteration. In a while loop, continue jumps back and re-tests the condition:
def main() -> Int32
n: Int32 = 0
while True:
n += 1
if n > 5:
break
if n % 2 == 1:
continue
print(n)
return 0
A while whose condition is already false never runs its body. One whose condition can't become false (and never breaks) runs forever.
Union narrowing is reset by a loop. Any type a union variable was narrowed to inside the body is thrown away afterwards; it reverts to its declared type. This is covered on the Unions page.
for
for runs a block once per item, with the loop variable holding each item in turn. Three things are iterable: the range helper from the standard library, Lists, and Dicts. (Any type with the right three methods is iterable too — see below.)
for ... in range(...)
range counts whole numbers. It takes one, two, or three arguments:
| Call | Yields |
|---|---|
range(n) | 0, 1, 2, …, n-1 |
range(a, b) | a, a+1, a+2, …, b-1 |
range(a, b, step) | a, then plus step each time, stopping before b. A negative step counts down. |
range(0) or range(a, a) | nothing: an empty run |
def main() -> Int32
for i in range(5):
print(i)
for j in range(2, 5):
print(j)
for k in range(10, 0, -3):
print(k)
return 0
range works with Int64, and the loop variable is an Int64 too. Count sums and accumulating loops, and you'll need an Int64 accumulator to add into:
def main() -> Int32
total: Int64 = 0
for i in range(5):
total += i
print(total)
return 0
for ... in List
Looping over a list yields each element in order. The loop variable has the list's element type. Working with a list of strings:
def main() -> Int32
names: List[String] = ["ann", "bob", "clyde"]
for name in names:
print(name)
return 0
A list literal works too, so for n in [1, 2, 3]: loops over the numbers 1, 2, and 3.
Growing and shrinking lists
Four methods change a list in place. append(value) adds to the end, pop() removes the last item, insert(index, value) puts a value at a position, and remove(value) deletes the first equal item. Each is a statement; the call updates the variable (or field, or list slot) it was made on.
def main() -> Int32
xs: List[Int32] = [1, 2, 3]
xs.append(4) # [1, 2, 3, 4]
xs.insert(0, 9) # [9, 1, 2, 3, 4]
xs.pop() # [9, 1, 2, 3]
xs.remove(2) # [9, 1, 3]
print(xs)
return 0
insert clamps its index like Python: an index past the end appends, a negative index counts from the end (then clamps at 0), and remove takes the first match. Element types come from the list: List[String] needs a String, and a literal works if it fits, e.g. ys.append(7) on a List[Int8]. Under --debug, pop on an empty list and remove of a missing element are runtime errors; without --debug their behavior is unspecified.
The same calls work on a list inside a struct (b.items.append(4)) or inside another list (grid[0].append(4)).
for ... in Dict
Looping over a dictionary yields its keys, one at a time — not the values. Look the value up with the key inside the body:
def main() -> Int32
ages: Dict[String, Int32] = {"ann": 30, "bob": 40}
for key in ages:
print(key, ages[key])
return 0
The loop variable carries the dictionary's key type, String here.
Your own iterables
Any type that provides three methods can be looped over: done() returning a Bool that says whether iteration is finished, value() returning the current item, and advance() returning a fresh value of the same type that moves to the next item. The standard library's range is built on exactly this — see Std lib.
The loop variable of a for behaves like a normal variable: it keeps its last value after the loop ends, and if you use the same name in a later loop the element type has to match. A for loop is a while loop in disguise, and like each while loop it accepts break and continue. In a for loop, continue still runs the loop's own bookkeeping before the next item, so the loop terminates normally even if the body jumps.
Recursion
A recursion example:
def fact(n: Int32) -> Int32
if n <= 1:
return 1
return n * fact(n - 1)
def main() -> Int32
print(fact(5))
return 0
Deep recursion can hit stack limits, so keep the depth sane. If you want repetition without eating stack, use while or for instead.