3. Collections and loops
Lists#
A list is written as in Python, and grows and shrinks as Python's does:
pub func main(arguments: list[str]) -> int!:
let primes = [2, 3, 5, 7]
primes.append(11)
print(primes, primes.length)
print(primes[0], primes[-1], primes[1..<3])
print(primes.contains(5), primes.index_of(7))
return 0[2, 3, 5, 7, 11] 5 2 11 [3, 5] true 3
Indexing starts at 0, and a negative index counts from the end. A slice is written with a range, primes[1..<3], and makes a new list. An index past the end stops the program, much as Python's IndexError does, except that it cannot be caught.
Two rules that Python does not have:
- All elements have one type.
[1, "a"]is an error. A list of integers is alist[int]. - An empty list needs its type written, since there is nothing to infer it from:
let names: list[str] = [].
Notice that primes is a let and still changed. A list is an object that bindings share, exactly as in Python: let stops primes from being bound to another list, but not the list from changing. A second binding refers to the same list:
pub func main(arguments: list[str]) -> int!:
let first = ["Ada"]
let second = first
second.append("Grace")
print(first)
let third = first.copy()
third.append("Barbara")
print(first, third)
return 0[Ada, Grace] [Ada, Grace] [Ada, Grace, Barbara]
copy() makes an independent list, as list(x) or x.copy() do in Python.
Maps#
A map is Python's dict: keys to values, kept in the order they were inserted.
pub func main(arguments: list[str]) -> int!:
let ages = {"Ada": 36, "Grace": 45}
ages["Barbara"] = 40
print(ages, ages.length, "Ada" in ages)
print(ages["Ada"], ages["Linus"])
let age = ages["Linus"] else 0
print(age + 1)
return 0{Ada: 36, Grace: 45, Barbara: 40} 3 true
36 none
1Looking up a key that is not there is not an error, as Python's KeyError would be. ages["Linus"] answers none, "no value", and its type says so: it is an int?, an optional int, rather than an int. You cannot do arithmetic on it until you say what should happen when it is missing. else supplies a value for that case, and ages.get("Linus", 0) is there too, as in Python. Chapter 7 covers optionals in full.
Sets and tuples#
A set holds each value once, as in Python, and also keeps insertion order. A tuple groups a fixed number of values that may have different types:
pub func main(arguments: list[str]) -> int!:
let seen = {3, 1, 2}
seen.insert(3)
print(seen, 2 in seen, seen.union({9}))
let pair = (1, "one")
let (number, name) = pair
print(pair, pair.0, number, name)
return 0{3, 1, 2} true {3, 1, 2, 9}
(1, one) 1 1 oneA tuple's members are .0, .1 and so on, or taken apart with let (a, b) = ... as in Python. {} is an empty map, as in Python, so an empty set is written with its type: let seen: set[int] = {}.
Loops#
for goes through a list, a set, a map, a string, or a range of numbers:
pub func main(arguments: list[str]) -> int!:
for i in 0..<3:
print(i)
let ages = {"Ada": 36, "Grace": 45}
for (name, age) in ages:
print(f"{name} is {age}")
for (index, name) in ["Ada", "Grace"].indexed():
print(index, name)
return 00 1 2 Ada is 36 Grace is 45 0 Ada 1 Grace
- Ranges are written
0..<3(0, 1 and 2, like Python'srange(3)) or1..=3(1, 2 and 3, the end included);(0..<10).step(2)and(1..=3).reversed()are Python's other forms ofrange. - A map gives key and value pairs, as Python's
ages.items()does, where Python'sfor name in agesgives only the keys. Useages.keys()for the keys alone. indexed()pairs each element with its position, as Python'senumeratedoes.
while, break and continue work as in Python. One more rule: changing a list's length while a for goes through it stops the program, instead of skipping or repeating elements as it can in Python. Collect the changes and apply them after the loop.
if and match#
if, elif and else are Python's. match chooses between cases, like Python's match statement or a chain of elifs, and it also works as an expression, producing a value:
pub func main(arguments: list[str]) -> int!:
for n in [0, 7, 42, -1]:
let word = match n:
0 => "zero"
1..<10 => "a digit"
_ if n < 0 => "negative"
_ => "many"
print(n, word)
return 00 zero 7 a digit 42 many -1 negative
Each arm is a pattern, an => and a value. A pattern can be a literal, a range, _ (any value), and can carry a condition, if n < 0. The arms are tried in order and the first that matches gives the value. A match must cover every possible value, which is why the last arm here is _. Chapter 5 shows where match is most useful: choosing between the cases of an enum.
Where next#
Chapter 4 is about functions: declaring them, calling them with named arguments, and passing them around as values.