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:
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:
topaz check operators-expressions.tpz
topaz run operators-expressions.tpz
The output is:
42 true truebase * 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
| Need | Form | Important distinction |
|---|---|---|
| Arithmetic | +, -, *, /, %, ** | Numeric operands stay in the same int or float domain |
| Ordering or equality | <, <=, >, >=, ==, != | Only comparable types are admitted |
| Membership | value in collection | A Map lookup uses its keys, not value in map |
| Conditional work | left && right, left || right | The right side runs only when needed |
| Default for absence | value ?? fallback | Coalesces a nullable or optional value |
| Result propagation | postfix value? | Returns an Err from the enclosing function; it is not ?? |
| Left-to-right flow | value |> next | Uses the pipeline rules rather than ordinary binary arithmetic |
| Mutation | target = 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 |>.