Skip to content

Control flow

Tessel has three control flow constructs: if for decisions, for for loops, and match for choosing between many cases. They work the same in functions and in views.

let temperature = 25
if temperature > 30 {
print("Hot")
} else if temperature > 15 {
print("Pleasant")
} else {
print("Cold")
}
Pleasant

The condition must be a Bool, and needs no parentheses. The braces around each body are required, even for a single line. else can go on the same line as the closing brace or at the start of the next line.

if is an expression: when every branch ends with a value, the whole if produces that value.

let score = 85
let grade = if score >= 90 { "A" } else if score >= 80 { "B" } else { "C" }
print(grade)
B

Every branch must produce the same type, and there must be an else, so that there is always a value. This is Tessel’s replacement for the condition ? a : b operator of other languages.

if let checks whether an optional has a value, and if so, gives you the value under a new name:

let input = "42"
if let number = Int(input) {
print("Twice that is {number * 2}")
} else {
print("{input} is not a number")
}
Twice that is 84

Inside the first block, number is a plain Int. It only exists there. You can chain else if let and else if as usual. See Optionals for more.

You can also write the type of the new name, after a colon. Usually that’s not needed, but it is for fromJson and fromBinary, which find out what to read from the type they’re given:

if let scores: [Int] = fromJson("[90, 75, 82]") {
print("Best score: {scores.max() ?? 0}")
}
Best score: 90

The type is the unwrapped type ([Int], not [Int]?).

for runs its body once for each item of a list, or each number in a range:

for i in 0..3 {
print("Round {i}")
}
let fruits = ["apple", "banana", "cherry"]
for fruit in fruits {
print(fruit.uppercase())
}
Round 0
Round 1
Round 2
APPLE
BANANA
CHERRY

a..b counts from a up to, but not including, b. If b is not larger than a, the loop doesn’t run at all.

If you don’t need the loop variable, name it _:

for _ in 0..3 {
print("Hip hip hooray!")
}

A range is a value of type Range, so you can also keep one in a variable or pass it to a function, and loop over it later:

fn countdown(_ steps: Range) {
for i in steps {
print(10 - i)
}
}
countdown(7..10) // prints 3, 2, 1

To loop over a list together with the position of each item, loop over a range of indexes:

let names = ["Ada", "Grace"]
for i in 0..names.count {
print("{i + 1}. {names[i]}")
}
1. Ada
2. Grace

A loop over a dictionary gives each entry’s key and value, in the order the keys were first added:

let stock = ["apples": 3, "pears": 0]
for (name, count) in stock {
print("{name}: {count}")
}
apples: 3
pears: 0

When you loop over a list stored in a var (or a state, or a bind parameter), the loop variable stands for the item in the list, so changing it changes the list:

var prices = [10, 20, 30]
for price in prices {
price *= 2
}
print(prices[0])
20

When the list can’t change (a let, a parameter, or the result of a call like filter), the loop variable is a constant.

while repeats its body as long as a condition is true. It checks the condition before each round, so the body may not run at all:

fn collatzSteps(n: Int) -> Int {
var steps = 0
var x = n
while x != 1 {
x = if x % 2 == 0 { x / 2 } else { 3 * x + 1 }
steps += 1
}
steps
}
fn main() {
print(collatzSteps(n: 27))
}
111

Use for when you’re going through a list or a range, and while when you don’t know in advance how many rounds it takes.

while let runs the body as long as an optional has a value, with the value named inside, like if let in a loop:

fn main() {
var queue = ["one", "two", "three"]
while let next = queue.first {
queue.removeFirst()
print("took {next}")
}
}
took one
took two
took three

In any loop, for or while:

  • continue skips the rest of the body and goes on with the next round.
  • break leaves the loop straight away.
fn main() {
for n in 1..100 {
if n % 2 == 0 {
continue // skip even numbers
}
if n > 7 {
break // done
}
print(n)
}
}
1
3
5
7

In nested loops, they apply to the innermost one. while true { … } with a break inside is a loop that decides when to stop in the middle:

fn main() {
var guess = 1
while true {
if guess * guess > 50 {
break
}
guess += 1
}
print("{guess} squared is the first square above 50")
}
8 squared is the first square above 50

A block ({ x in … }) is a small function of its own, so break and continue inside it can’t leave a loop outside it; use return to leave the block. And return still leaves the whole function, from inside any loop:

fn firstNegative(numbers: [Int]) -> Int? {
for n in numbers {
if n < 0 {
return n
}
}
nil
}
fn main() {
print(firstNegative(numbers: [4, -2, -7]) ?? 0)
}
-2

A view body can’t use while, break or continue: it describes what to show, so it uses for to show views for each item, and if to choose. Loops with while, break and continue belong in functions and actions.

match compares a value against a list of patterns, and runs the arm of the first one that fits. Each arm is pattern -> result:

enum Weather { sunny, rainy, snowy }
fn advice(weather: Weather) -> String {
match weather {
.sunny -> "Wear sunglasses"
.rainy -> "Take an umbrella"
.snowy -> "Wear boots"
}
}
fn main() {
print(advice(weather: .rainy))
}
Take an umbrella
PatternMatches
.namethe enum case name (whatever values it carries)
.name(a, b)the enum case name, and names its values a and b
.name(a, _)the same, ignoring the second value
42, -1, 2.5, "text", truea value equal to this literal
_anything
fn describe(n: Int) -> String {
match n {
0 -> "zero"
1 -> "one"
-1 -> "minus one"
_ -> "many"
}
}

Patterns can’t be nested or combined, and there are no ranges or conditions in patterns. Use if for those.

A match must cover every possible value. For an enum, that means an arm for every case; for a Bool, arms for true and false. For other types, like Int and String, there are too many values to list, so you need a _ arm. If something is missing, tessel check tells you what:

error: this `match` doesn't handle `.snowy`
= help: add arms for them, or `_ -> …` to handle the rest

_ must be the last arm, because any arms after it could never run.

To run more than one line in an arm, put the arm’s body in braces:

let count = 2
match count {
0 -> print("none")
_ -> {
let doubled = count * 2
print("{count}, doubled is {doubled}")
}
}
2, doubled is 4

Braces are also needed when an arm changes a variable, because an assignment isn’t a value:

match mode {
.light -> { brightness = 1.0 }
.dark -> { brightness = 0.2 }
}

Without the braces, .light -> brightness = 1.0 is an error, and tessel check suggests adding them.

Like if, match is an expression. Its value is the value of the arm that ran:

let answer = "yes"
let accepted = match answer {
"yes" -> true
"y" -> true
_ -> false
}
print(accepted)
true

if, for and match also build user interfaces: inside a view, they decide which views to show, and for creates one view per item. See Apps and views and Lists and identity.