Lesson 6 of 25 · Control flow

Conditions

Conditions are how a program decides, from its data, which way to go. Salam keeps them strict: a condition is always a real bool, and the compiler points out branches that could never run.

if, else if, else

The body of an if runs only when its condition holds. Test further conditions with else if, and cover everything that is left with else. The conditions are tried from the top, and only the first one that holds runs, so put the narrowest test first.

conditions.salam
func grade(score: int): str:
    if score >= 90:
        ret "excellent"
    else if score >= 70:
        ret "good"
    else:
        ret "needs more work"
    end
end

func main:
    println grade(95)
    println grade(72)
    println grade(40)
end
Outputexcellent good needs more work

A score of 95 is also at least 70, but the program prints only excellent: once a branch has run, the rest are skipped.

Short conditions, and joining them

A short condition fits on one line, with end at the end of the line. Join several conditions with and and or.

cond-inline.salam
func main:
    number := 7
    if number % 2 == 0: println "even" else: println "odd" end
    if number > 0 and number < 10:
        println "a positive single digit number"
    end
end
Outputodd a positive single digit number

Comparison words

Every comparison operator also has a word: eq for ==, neq for !=, lt and gt for < and >, and lte and gte for <= and >=. Use whichever reads better; they mean exactly the same. The example also shows the short form of else if: else followed directly by a condition.

cond-words.salam
func ticket(age: int): int:
    if age lt 4:
        ret 0
    else age lte 12:
        ret 40
    else age gte 65:
        ret 50
    else:
        ret 90
    end
end

func main:
    println ticket(2), ticket(9), ticket(30), ticket(70)
    seats := 12
    sold := 12
    if sold eq seats: println "sold out" end
    if sold neq seats: println "seats left" end
end
Output0 40 90 50 sold out

and, or and not

and and or stop as soon as the answer is known. If the left side of and is false, the right side is never evaluated, and the same goes for or when the left side is true. That makes the left side a guard for the right: test that a count is not zero before dividing by it, or that an index is in range before reading the array.

cond-short.salam
func average(total: int, count: int): int:
    if count != 0 and total / count > 0:
        ret total / count
    end
    ret 0
end

func main:
    println average(270, 3), average(0, 0)
    scores := [12, 18, 7]
    i := 5
    if i < len(scores) and scores[i] > 10:
        println "a high score"
    else:
        println "no score at", i
    end
    logged_in := true
    banned := false
    if logged_in and not banned:
        println "welcome back"
    end
    stock := 3
    if not (stock > 5):
        println "time to reorder"
    end
end
Output90 0 no score at 5 welcome back time to reorder

not binds tighter than a comparison. not stock > 5 would mean (not stock) > 5, which is an error because stock is not a bool. Write not (stock > 5), or simply stock <= 5. There is no &&, || or ! in Salam; the compiler names the word to write instead.

Choosing a value

A variable declared inside a branch disappears at that branch's end. When each branch decides a value, declare the variable with mut before the if and assign it in the branches. For a choice between just two values, the ternary operator condition ? a : b is shorter, and both sides must have the same type.

cond-scope.salam
func main:
    weight := 3
    express := true
    mut cost := 0
    if weight <= 1:
        cost = 30
    else weight <= 5:
        cost = 55
    else:
        cost = 90
    end
    if express:
        cost += 25
    end
    println "shipping:", cost
    label := weight > 2 ? "heavy" : "light"
    println label
end
Outputshipping: 80 heavy

Returning early

Deeply nested ifs are hard to read. Inside a function, check for each problem in turn and ret as soon as one is found. What is left at the bottom is the normal case, with no nesting at all.

cond-guard.salam
func check_order(qty: int, stock: int, paid: bool): str:
    if qty <= 0:
        ret "the quantity must be at least 1"
    end
    if qty > stock:
        ret "only " + stock + " left in stock"
    end
    if not paid:
        ret "waiting for payment"
    end
    ret "order accepted"
end

func main:
    println check_order(0, 10, true)
    println check_order(12, 10, true)
    println check_order(2, 10, false)
    println check_order(2, 10, true)
end
Outputthe quantity must be at least 1 only 10 left in stock waiting for payment order accepted

Comparing floats

Most decimal fractions cannot be stored exactly in binary, so a little rounding error creeps into arithmetic. 0.1 + 0.2 comes out as 0.30000000000000004, and == against 0.3 is false. Compare floats by asking whether their difference is small enough. For money, keep amounts in whole units (cents, rials) as integers, or use the decimal package.

cond-float.salam
import math

func main:
    paid := 0.1 + 0.2
    price := 0.3
    println paid
    if paid == price:
        println "equal"
    else:
        println "not equal"
    end
    if math.Abs(paid - price) < 0.000001:
        println "close enough"
    end
end
Output0.30000000000000004 not equal close enough

The rules a condition follows

  • A condition has to be a bool. if number: is an error; write if number != 0:.
  • if x = 3: is an error that tells you to write == (or eq).
  • if x == 1 or 2: is an error too, because 2 is not a bool. Write x == 1 or x == 2.
  • A condition that is always true, or always false, such as if 2 > 1:, is rejected, because one of the branches would never run.
  • No branch may be empty. Instead of an empty branch, invert the condition with not.
  • A variable declared inside a branch cannot be used after the branch's end.

When one value is compared against many possible cases, a long else if chain gets tiresome. The next lessons cover loops, and then match and switch, which are made for exactly that.