Language Foundations

Operators & Expressions

Follow the values produced by arithmetic, comparison, short-circuit, and assignment forms.

Use an expression when you need a value. Arithmetic, comparison, a function call, and an if or match can all produce one. Assignment has a different job: it changes an existing mutable location and is a statement, not a value that can be embedded in another expression.

Start with a value you can observe

Save this as operators-expressions.tpz:

TOPAZ
function score(base: int, bonus: int) -> int {
    let doubled = base * 2
    if bonus > 0 { doubled + bonus } else { doubled }
}

let result = score(20, 2)
let exact = result == 42
let changed = result != 41

print("{result} {exact} {changed}")

Check and run it:

BASH
topaz check operators-expressions.tpz
topaz run operators-expressions.tpz

The output is:

Output
42 true true

base * 2 is evaluated before + bonus, and the chosen if branch becomes the function result. == asks whether two comparable values are equal; != asks the opposite. Neither changes a binding.

Choose an operator by its job

NeedFormImportant distinction
Arithmetic+, -, *, /, %, **Numeric operands stay in the same int or float domain
Ordering or equality<, <=, >, >=, ==, !=Only comparable types are admitted
Membershipvalue in collectionA Map lookup uses its keys, not value in map
Conditional workleft && right, left || rightThe right side runs only when needed
Default for absencevalue ?? fallbackCoalesces a nullable or optional value
Result propagationpostfix value?Returns an Err from the enclosing function; it is not ??
Left-to-right flowvalue |> nextUses the pipeline rules rather than ordinary binary arithmetic
Mutationtarget = value, +=, ??=A statement; it does not itself produce a value

&& evaluates its right side only when the left side is true. || evaluates its right side only when the left side is false. This makes short-circuiting suitable for guarding work that is only valid after an earlier condition succeeds. ?? also skips its fallback when the left side already contains a value. Postfix ? is different again; the Null, Option, Result & Faults page explains its enclosing-function boundary.

Numeric and equality boundaries

int is a signed 64-bit integer. Integer division truncates toward zero, and remainder has the sign of the dividend. Integer overflow, division by zero, and a dynamically negative integer exponent are runtime faults; the same invalid operation in a constant expression is a static error. float uses IEEE-754 binary64, so floating division by zero and NaN follow that model. Topaz does not implicitly convert between int and float.

Primitive values, compatible records, Arrays, Options, Results, and nominal values are comparable only when their contained types are comparable. Array equality observes length and element order. Map and Set values, functions, resources, and template values are not comparable. Nominal equality first requires the same enum, record, or newtype identity.

Precedence places calls and member access above exponentiation, unary operators, multiplication, addition, ranges, comparisons, &&, ||, ??, composition, and finally |>. You rarely need to memorize the whole order: use parentheses whenever grouping carries the explanation.

Common correction

Do not write count++, count--, or count **= 2; those forms are not Topaz. For a mutable binding, write an explicit statement such as count = count + 1. Do not use = when you mean equality: = changes a mutable target, while == produces a boolean.

Bitwise operators, implicit numeric promotion, floating remainder as an operator, operator overloading, and user-defined operators are outside the current surface. Function composition exists only at the specification level for this documentation profile; ordinary current examples use named functions or |>.

Continue with Types for the domains operators accept, Patterns & Control Flow for value-producing branches, and Pipelines & Placeholders for |>.