Lesson 4 of 25 · Getting started

Operators

Operators work on values: they do arithmetic, compare things and join conditions together. Most of them look the way you expect; this lesson spends its time on the few places where they do not.

Arithmetic

+ - * / % behave as they do in mathematics, and ** raises to a power. Multiplication and division happen before addition and subtraction, and parentheses change the order.

ops-arith.salam
func main:
    a := 17
    b := 5
    println a + b, a - b, a * b, a / b, a % b
    println 2 ** 10
    println (2 + 3) * 4, 2 + 3 * 4
end
Output22 12 85 3 2 1024 20 14

+ also joins strings, and with a string on its left it turns a number into text: "total: " + 5 is "total: 5". The strings lesson has more.

Division and remainder

Dividing two whole numbers gives a whole number: 17 / 5 is 3, and % gives what is left over, 2. That pair is exactly what you need to split minutes into hours, to count how many tables a group needs, or to tell even numbers from odd ones.

ops-divide.salam
func main:
    minutes := 135
    println minutes / 60, "h", minutes % 60, "min"
    guests := 17
    per_table := 4
    tables := (guests + per_table - 1) / per_table
    println "tables needed:", tables
    println "empty seats:", tables * per_table - guests
    n := 7
    println n, "is", n % 2 == 0 ? "even" : "odd"
    println -7 / 2, -7 % 2
end
Output2 h 15 min tables needed: 5 empty seats: 3 7 is odd -3 -1

Two details are worth remembering:

  • Division cuts toward zero, so -7 / 2 is -3, and the remainder takes the sign of the left side, so -7 % 2 is -1. A test such as n % 2 == 1 therefore misses negative odd numbers; n % 2 != 0 catches them all.
  • % works only on whole numbers; on decimals it is a compile error. Dividing by a literal 0 is a compile error too, but a zero that turns up at run time is not checked for you and can crash the program, so test the divisor first.

Powers

** raises a number to a power, and its result is always an f64, even for two whole numbers. That lets it handle 2 ** 0.5 and 2 ** -1, but it means you convert with as int when you need a whole number back.

ops-power.salam
func main:
    bacteria := 100 * 2 ** 10
    println "after ten doublings:", bacteria
    println 2 ** 0.5, 2 ** -1
    println 2 ** 3 ** 2, -2 ** 2, (-2) ** 2
    cells := (2 ** 10) as int
    println cells / 3
end
Outputafter ten doublings: 102400 1.4142135623730951 0.5 512 -4 4 341

** groups from the right, so 2 ** 3 ** 2 is 2 ** 9, and it binds tighter than a leading minus, so -2 ** 2 is -4, as in mathematics. For the same reason **= works on an f64 variable but not on an int one, because the result would not fit back in.

Comparison

A comparison produces a bool. The operators are ==, !=, <, >, <= and >=, and each has a word form as well: eq, neq, lt, gt, lte and gte. Join conditions with and and or, and invert one with not.

ops-compare.salam
func main:
    age := 20
    println age > 18, age < 18, age == 20, age != 20
    println age eq 20, age neq 30
    println age gt 18, age lte 20
    println age >= 18 and age <= 60
    println age < 10 or age > 15
    println not (age > 18)
    println "apple" < "banana", "apple" == "apple"
end
Outputtrue false true false true true true true true true false true true

Strings compare with the same operators. == checks that the text is the same, and < orders by character code, so every capital letter comes before every small one: "Zebra" < "apple" is true.

Comparisons do not chain. 1 < x < 10 compares the bool from 1 < x with 10, and the compiler rejects it (cannot compare 'bool' and 'i32'); write x > 1 and x < 10.

Combining conditions

and and or stop as soon as the answer is known. If the left side of and is false, the right side never runs; if the left side of or is true, the same. That makes it safe to guard a risky check with a simple one, as the first if does here: the division only happens when count is not zero.

ops-logic.salam
func check(name: str): bool:
    println "  checked", name
    ret true
end

func main:
    count := 0
    total := 50
    if count > 0 and total / count > 10:
        println "large average"
    else:
        println "no data yet"
    end
    println false and check("a")
    println true or check("b")
    println true and check("c")
    logged_in := true
    banned := false
    println "may post:", logged_in and not banned
end
Outputno data yet false true checked c true may post: true

Only the third call to check ran. not binds tighter than any comparison, so not count == 3 means (not count) == 3 and is an error; write not (count == 3), or simply count != 3. Salam has no &&, || or !; the compiler rejects them and names the word to write instead.

The ternary operator

condition ? a : b gives a when the condition holds and b otherwise. Ternaries can be chained to pick from several ranges, and they read top to bottom: the first condition that holds wins.

ops-ternary.salam
func main:
    temperature := 32
    weather := temperature > 30 ? "hot" : "mild"
    println "the weather is", weather
    score := 73
    grade := score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F"
    println "grade:", grade
    items := 1
    println items, items == 1 ? "item" : "items"
end
Outputthe weather is hot grade: C 1 item

Both branches must have the same type: n > 3 ? "big" : 0 is an error. A chain of more than three or four cases is usually clearer as a match.

Changing a variable in place

+=, -=, *=, /= and %= apply an operator and store the result back in the variable, which must be mut. += also appends to a string. ++ adds one and -- takes one away.

ops-compound.salam
func main:
    mut balance := 100
    balance += 50
    balance -= 30
    balance *= 2
    balance /= 3
    balance %= 50
    println "balance:", balance
    mut log := "start"
    log += ", saved"
    log += ", files: " + 3
    println log
    mut i := 5
    i++
    i--
    i++
    j := i++
    println "j:", j, "i:", i
end
Outputbalance: 30 start, saved, files: 3 j: 6 i: 7

Used as a value, i++ gives the old value and then adds one, which is why j is 6 while i ends at 7; ++i adds first. Code is easier to read when ++ and -- stand on their own line.

Bitwise operators

&, |, ^, ~ and the shifts << and >> work on the bits of a whole number.

ops-bitwise.salam
func main:
    a := 0b1100
    b := 0b1010
    println a & b, a | b, a ^ b
    println 1 << 4, 256 >> 2, ~0
end
Output8 14 6 16 64 -1

Their everyday use is a set of yes or no options packed into one number, each option one bit. |= turns an option on, &= ~ turns it off and & tests it. A shift right by 10 divides by 1024.

ops-flags.salam
const READ := 1
const WRITE := 2
const EXEC := 4

func main:
    mut perms := READ | WRITE
    println "can write:", (perms & WRITE) != 0
    perms |= EXEC
    perms &= ~WRITE
    println "can write:", (perms & WRITE) != 0
    println "can run:", (perms & EXEC) != 0
    println "bits:", perms
    println "5000 bytes in KiB:", 5000 >> 10
end
Outputcan write: true can write: false can run: true bits: 5 5000 bytes in KiB: 4

Keep the parentheses in (perms & WRITE) != 0. As in C, & binds more loosely than == and !=, so without them the comparison happens first. The compiler catches this one, since it leaves & with a bool on one side. Bitwise operators accept whole numbers only.

Which operator goes first

From the tightest binding to the loosest. Operators on the same row are worked out from left to right, except **.

OperatorsExample
asx / 2 as f64 converts only the 2
** (right to left)2 ** 3 ** 2 is 512
unary -, ~, not-2 ** 2 is -4
*, /, %2 + 3 * 4 is 14
+, -1 << 4 + 1 is 32
<<, >>1 << 2 < 5 is true
<, <=, >, >=
==, !=
&, then ^, then |6 & 3 | 8 is 10
and
ortrue or true and false is true
? :ok ? 1 : 2 + 10 is 1 or 12

When you have to stop and think about the order, add parentheses. They cost nothing and the next reader will not have to think either.

Two plus or minus signs in a row, as in a - -b, is an error; write a + b, or put the inner expression in parentheses: a - (-b). Your own types can define what these operators do; see the new types lesson.