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.
if and else
Section titled “if and else”let temperature = 25if temperature > 30 { print("Hot")} else if temperature > 15 { print("Pleasant")} else { print("Cold")}PleasantThe 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 as a value
Section titled “if as a value”if is an expression: when every branch ends with a value, the whole if
produces that value.
let score = 85let grade = if score >= 90 { "A" } else if score >= 80 { "B" } else { "C" }print(grade)BEvery 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
Section titled “if let”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 84Inside 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: 90The type is the unwrapped type ([Int], not [Int]?).
for loops
Section titled “for loops”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 0Round 1Round 2APPLEBANANACHERRYa..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, 1To 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. Ada2. GraceLooping over a dictionary
Section titled “Looping over a dictionary”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: 3pears: 0Changing items while looping
Section titled “Changing items while looping”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])20When the list can’t change (a let, a parameter, or the result of a call
like filter), the loop variable is a constant.
while loops
Section titled “while loops”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))}111Use 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
Section titled “while let”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 onetook twotook threebreak and continue
Section titled “break and continue”In any loop, for or while:
continueskips the rest of the body and goes on with the next round.breakleaves 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) }}1357In 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 50A 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)}-2A 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 umbrellaPatterns
Section titled “Patterns”| Pattern | Matches |
|---|---|
.name | the 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", true | a 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.
Every value must be handled
Section titled “Every value must be handled”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.
Arms with several lines
Section titled “Arms with several lines”To run more than one line in an arm, put the arm’s body in braces:
let count = 2match count { 0 -> print("none") _ -> { let doubled = count * 2 print("{count}, doubled is {doubled}") }}2, doubled is 4Braces 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.
match as a value
Section titled “match as a value”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)trueIn views
Section titled “In views”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.