Threadon / Flow

Flow

decisions and repetition

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:

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
great ok try again

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
0 1 2

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
2 4

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:

CallYields
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
0 1 2 3 4 2 3 4 10 7 4 1

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
10

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
ann bob clyde

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
ann 30 bob 40

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
120

Deep recursion can hit stack limits, so keep the depth sane. If you want repetition without eating stack, use while or for instead.