Loops
A loop does something several times: a fixed number of times, over a range, while a condition holds, or once for every item in a list. Salam has three loop words, repeat, until and each, and each one fits a different job.
repeat
repeat 3: runs the body three times. Add in i to get a counter called i that starts at zero, so repeat 4 in i gives 0, 1, 2 and 3. The count may be any whole number expression, such as a variable or v.len(), and a count of zero runs the body no times at all. repeat 1 to 10 walks a range with both ends included, by 3 sets the step, and if the start is greater than the end the loop counts down.
func main:
repeat 3:
println "hello"
end
repeat 4 in i:
print i, ""
end
println ""
repeat 10 to 0 by 5 in i:
println "counting down", i
end
endThe counter belongs to the loop: assigning to i inside the body is a compile error, so the loop always runs the number of times its header says.
Ranges and steps
The step after by is always written as a positive number, even when the loop counts down: the bounds alone decide the direction, so repeat 10 to 1 by 3 gives 10, 7, 4 and 1. The step may be a variable. A step of 0, or a negative number written in the code such as by -2, is a compile error (E063). A step held in a variable is only known at run time and is not checked: if it turns out negative, the loop quietly runs zero times.
func main:
repeat 1 to 10 by 3 in i:
println "round", i
end
repeat 10 to 1 by 3 in i:
print i, ""
end
println ""
step := 15
repeat 0 to 60 by step in minute:
print minute, ""
end
println ""
endThe direction is decided when the loop starts, from the values the bounds have at that moment. That leads to a trap when a bound is a variable. repeat from to 1 counts down from 3 as expected, but when from is 0 the start is below the end, so the loop counts up and prints 0 1 instead of nothing:
func countdown(from: int):
repeat from to 1 in i:
print i, ""
end
println "go"
end
func safe_countdown(from: int):
if from >= 1:
repeat from to 1 in i:
print i, ""
end
end
println "go"
end
func main:
countdown(3)
countdown(0)
safe_countdown(0)
endIf the start can fall below the end, guard the loop with an if, as safe_countdown does.
The until loop
until condition: repeats for as long as the condition holds, and stops once it becomes false. It is the loop other languages call while; Salam has no while keyword. So write the condition the way you want the loop to carry on: until n != 0 keeps going while n is not zero. Reach for until when you do not know in advance how many turns are needed.
func digit_sum(number: int): int:
mut n := number
mut sum := 0
until n != 0:
sum += n % 10
n = n / 10
end
ret sum
end
func main:
println digit_sum(2026), digit_sum(0)
mut savings := 1000.0
mut years := 0
until savings < 2000.0:
savings = savings * 1.08
years += 1
end
println "doubled after", years, "years"
endThe condition is checked before every turn, including the first. That is why digit_sum(0) gives 0: the body never runs. It is also why an inverted condition is dangerous. Write until n == 0 by mistake and the loop runs zero times, with no error at all. When a loop seems to do nothing, check which way round its condition is before anything else.
Loops that end with break
Sometimes the place to decide whether to stop is the middle or the end of the body, not the top. Write until true: and leave with break where the decision falls. This is also how to write a loop that runs at least once, which other languages call do-while. The example pages through 23 search results, 10 to a page, and stops after the page that holds the last result.
func main:
total_items := 23
page_size := 10
mut page := 1
until true:
first := (page - 1) * page_size + 1
mut last := page * page_size
if last > total_items:
last = total_items
end
println "page", page, "shows items", first, "to", last
if last == total_items:
break
end
page += 1
end
enduntil false:, on the other hand, is a compile error, because its body could never run.
each ... in
each item in list walks every element of an array, a vector or a map. The form each index, item in list gives you the position as well.
func main:
cities := ["Tehran", "Isfahan", "Shiraz"]
each city in cities:
println city
end
each number, city in cities:
println number + 1, city
end
endA map always needs two names, each key, value in map, and a name you do not need is written _, because an unused loop variable is an error. The loop variable is a copy of the element and cannot be assigned. To change the elements themselves, take the index and write through it, as the price update below does.
func main:
mut stock := HashMap {} as HashMap<str, int>
defer stock.free()
stock.put("pen", 12)
stock.put("ink", 0)
stock.put("paper", 250)
stock.put("clip", 3)
mut units := 0
mut low := 0
each _, count in stock:
units += count
if count < 5:
low += 1
end
end
println "units in stock:", units
println "items to reorder:", low
mut prices := [100, 250, 40]
each i, price in prices:
prices[i] = price + price / 10
end
println prices
endA map is walked in its own internal order, not the order the keys were added, so do not rely on that order; this example only adds things up. A string cannot be walked with each directly. To visit its characters, walk str.Chars(s), which is covered in the strings lesson.
Nested loops
A loop inside another loop runs completely for every turn of the outer one. Here the outer loop picks a row, the inner loop fills its columns, and fmt.PadLeft keeps the numbers lined up.
import fmt
func main:
repeat 1 to 4 in row:
mut line := ""
repeat 1 to 6 in col:
line += fmt.PadLeft("" + row * col, 4)
end
println line
end
endbreak and continue
break leaves the loop and continue jumps to the next turn. Inside nested loops, break 2 leaves two loops at once, break 3 three, and so on; the number cannot be larger than the number of loops around it.
func main:
repeat 1 to 10 in i:
if i % 2 == 0:
continue
end
if i > 7:
break
end
print i, ""
end
println ""
repeat 3 in a:
repeat 3 in b:
if a + b == 3:
break 2
end
println a, b
end
end
endIn an until loop, be careful with continue: it jumps straight back to the condition, so if the line that moves the loop forward comes after the continue, it is skipped and the loop never ends. Update the counter before you continue.
Which loop to use
| You want to | Write |
|---|---|
| Do something a known number of times | repeat n: or repeat n in i: |
| Walk a range of numbers | repeat a to b by step in i: |
| Visit every element of a collection | each x in xs: or each i, x in xs: |
| Keep going while something is true | until condition: |
| Decide in the middle of the body, or run at least once | until true: with break |
Rules worth remembering
untilloops while its condition is true. An inverted condition makes the loop run zero times without any error.- The direction of
repeat a to bcomes from the bounds when the loop starts, so a variable start below the end counts up. - The step after
byis positive. Zero or a negative literal is a compile error; a negative step held in a variable makes the loop silently run zero times. - Loop variables cannot be assigned. Change a collection through its index.
- A loop that could never run, such as
until false:, is a compile error.
Loops over arrays, vectors and maps come back in the lessons on arrays and collections. To pick one of several cases inside a loop, see match and switch.