Control Flow

CDot includes most of the familiar control-flow statements you know from other languages, along with some slightly modified ones. This chapter provides an overview of what they are and how to use them.

If-Statements

if statements are the simplest way to change control flow in a program. An if statement takes a condition, and the code surrounded by the if is only executed when that condition evaluates to true. Optionally, you can provide an else clause to execute if the condition is false, which can itself be another if statement.

1
2
3
4
5
6
7
8
9
if calculateComplexCondition() {
    print("First condition was true 😃!")
}
else if calculateEvenMoreComplexCondition() {
    print("Other condition was true 🙂!")
}
else {
    print("No condition was true 😢...")
}

Note

The condition value does not have to be of type Bool, any value that conforms to the TruthValue protocol will do.

Instead of evaluating a boolean condition, an if statement can also bind a value, in which case the conditional code is only executed if that value is truthy. This is referred to as a binding statement, because the condition being evaluated is bound to a name that can be referenced in the if block. As an example, any Optional value is considered truthy if it actually stores a value, in which case the stored value will be bound by the if statement.

1
2
3
4
let mayHaveAValue: String? = "Good day to you"
if let value = mayHaveAValue {
    print(value)
}

As you can see, binding if statements are introduced using if let followed by the name to give the bound value. The previous example can be read as “if ‘mayHaveAValue’ actually has a value, unwrap it and bind it to a variable named ‘value’”.

Note

Similar to a normal if, if let statements are also not restricted to using Optional values. Any value that conforms to the Unwrappable protocol can be used in a binding statement.

There is a third spelling of an if statement that allows you to pattern match a value. This is explained in a later chapter.

While-Statements

A while statement is similar to an if statement in that it allows you to specify a condition in the same way. The behaviours differs though, because the code in a while statement will be executed as long as the condition remains truthy, which can be zero or infinitely many times. Because of this, a while statement can not specify an else clause.

1
2
3
4
5
6
7
8
9
var n = Int.random()
while n > 1 { // May or may not ever terminate...
    if n % 2 == 0 {
        n = n / 2
    }
    else {
        n = 3 * n + 1
    }
}

Loop-Statements

loop statements are very similar to while statements, only that the condition is evaluated after the execution of the body, which means that the code is guaranteed to be executed at least once. You can also completely leave out the condition to create an infinite loop.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var i = 0

// This runs at least once
loop {
    print(i)
    i += 1
} while Int.random() != 100

// This loop is semantically the same as the previous one
loop {
    print(i)
    i += 1

    if Int.random() == 100 {
        break
    }
}

For-Statements

CDot supports traditional C-Style for loops, which consist of semicolon-separated initialization, termination and increment parts. The initialization statement is executed once at the start of the loop, while the increment is executed after every iteration. The loop terminates once the termination evaluates to false for the first time.

1
2
3
for var i = 0; i < 5; ++i {
    print(i, terminator: " ") // Prints "1 2 3 4 5 "
}

Note

There is almost no good reason to use this style of for-loops in CDot, unless you want very fine-grained control over your program’s control-flow. In most cases, the for-in variant explained in the next section should be preferred.

For-In-Statements

The for-in statement is a specialization of the usual for loop, which allows you to iterate over any arbitrary sequence of elements. To replicate the behaviour of the above for loop, you can simply iterate over a Range:

1
2
3
for i in 0..5 {
    print(i, terminator: " ")
}

This can be read as for every value in the sequence, assign that value the name ``i`` and execute the loop body. This works for any type that implements the Iterable protocol, which includes any standard library collection:

1
2
3
4
5
6
let favoriteIceCreamFlavors = ["Strawberry", "Vanilla", "Mint"]
for flavor in favoriteIceCreamFlavors {
    if flavor == "Mint" {
        print("ewwww!")
    }
}

Match-Statements

match statements are CDot’s variant of the familiar switch from other languages, but much more powerful. You can match the given value against any arbitrary pattern as explained in Pattern Matching. In the simplest case, a match statement can look familiar to any ol’ switch:

1
2
3
4
5
6
7
8
match readValue() {
case 0:
    print("read a zero!")
case 1:
    print("read a one!")
default:
    print("read an unknown value!")
}

This will execute the block after the first case if readValue() evaluates to zero, the second block if it evaluates to one, and the default block otherwise. Unlike C, match statements in CDot need to be exhaustive, which means that you either need to cover every possible value or you need to provide a default block.

Note

There is no explicit fallthrough in a match statement, which means that a break is optional in a case block. If you need fallthrough behavior, you can explicitly ask for it with the continue keyword.

You can also provide custom matching behavior for any type. Range s for example, provide matching for the type of the range bounds:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
match 121 {
case 0..10:
    print("very small")
case 10..500:
    print("large-ish")
default:
    print("large")
}

// Prints "large-ish"

More complex patterns are explained in Pattern Matching.

Break & Continue

Loops introduced with while, loop or for support break and continue statements within the loop body. The break statement will immediately end the execution of the loop and prevent any future iterations, regardless of whether or not the underlying condition value has changed. The continue keyword can be used to skip the rest of the current iteration and go on to the next one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Prints "1 3 5 7 9"
for i in 0..10 {
    if i % 2 == 0 {
        continue // Continue to the next iteration without printing
    }

    print(i)
}

loop {
    if Int.random(upperBound: 10) ==  5 {
        break // Exit the loop when the random value is equal to 5
    }
}

Labeled Loops

CDot also supports labeled loops in case you need more fine-grained control over which loop you want to break or continue from. A labeled loop is introduced by writing a label name followed by a colon before the actual loop:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Try to figure out what this will print!
outer: for i in 0..5 {
    for j in 0..5 {
        if i + j == 5 {
            continue outer
        }

        print("${i} ${j}")
    }
}

In contrast to most other languages, CDot also allows you to break out of an if statement, but only if it is explicitly labeled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if myCondition() {
    break // error: can't break in unlabeled if
}

my_if: if complexCondition() {
    if otherComplexCondition() {
        break my_if // Allowed
    }

    doSomething()
}