Skip to content

Control Flow

Almide has a small set of control flow constructs. if and match are expressions that return values. for and while are statements that return Unit.

if is an expression. Both branches must have the same type:

let label = if score >= 90 then "A" else "B"

Chaining:

let grade = if score >= 90 then "A"
else if score >= 80 then "B"
else if score >= 70 then "C"
else "F"

if without else returns Unit and is used for side effects only:

if verbose then println("debug info")

The condition must be Bool. There is no truthiness — if 1 or if "hello" are compile errors.

Boolean operators use words, not symbols:

if x > 0 and x < 100 then "in range" else "out of range"
if not found or expired then "unavailable" else "ok"

for iterates over lists and ranges:

let names = ["Alice", "Bob", "Charlie"]
for name in names {
println(name)
}
for i in 0..5 { // 0, 1, 2, 3, 4 (exclusive end)
println(int.to_string(i))
}
for i in 1..=5 { // 1, 2, 3, 4, 5 (inclusive end)
println(int.to_string(i))
}

Ranges in for loops are optimized: no list is allocated.

Destructure tuples directly in the loop variable:

for (key, value) in map.entries(config) {
println("${key} = ${value}")
}
for (i, item) in list.enumerate(items) {
println("${int.to_string(i)}: ${item}")
}

Use _ to ignore a component:

for (_, value) in map.entries(config) {
println(value)
}

Iterating a map directly yields keys:

for key in m {
println(key)
}

Use map.entries(m) to iterate key-value pairs.

while loops repeat while a condition holds:

var i = 0
while i < 10 {
println(int.to_string(i))
i = i + 1
}

The condition must be Bool. The loop body returns Unit.

break exits a loop early. continue skips to the next iteration:

var i = 0
while i < 100 {
i = i + 1
if i % 2 == 0 then continue
if i > 10 then break
println(int.to_string(i))
}

These work in both for and while loops.

guard checks a precondition and exits early when the condition is false:

effect fn validate(x: Int) -> Result[Int, String] = {
guard x > 0 else err("must be positive")
guard x < 1000 else err("too large")
ok(x * 2)
}

The else branch must return the enclosing function’s return type.

Guard with a block body:

guard not fs.exists?(path) else {
println("already exists")
ok(())
}

guard is the primary way to do early returns. There is no return keyword in Almide.

See Error Handling for more on guard with Result.

OperatorMeaning
==Deep equality
!=Deep inequality
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal

Comparison operators are non-associative: a < b < c is a compile error. Use a < b and b < c.

OperatorMeaning
+Addition, or string/list concatenation
-Subtraction
*Multiplication
/Division
%Remainder
^Exponentiation (right-associative)

^ is power, not XOR. ** is accepted as an alias for ^. For bitwise XOR, use int.bxor(a, b).

OperatorMeaning
andLogical AND (short-circuit)
orLogical OR (short-circuit)
notLogical NOT (prefix)

&&, ||, and ! are rejected by the compiler with hints to use and, or, and not.

LevelOperators
Highest. () []
not - (unary)
^ (power, right-assoc)
* / %
+ - ++
.. ..= (range, non-assoc)
== != < > <= >= (non-assoc)
and
or
Lowest|> (pipe) >> (compose)