Skip to content

Language reference

Conditions, loops and matching

Choose values, iterate over arrays and ranges, and exit loops safely.

Bool has the values true and false. Comparisons return Bool. Use not, and and or for Boolean logic. and and or evaluate only the operands needed to determine the result.

Select a result

kalandra
fn clampLow(value: int, minimum: int) -> int:
  if value < minimum:
    minimum
  else:
    value

fn main() -> int:
  clampLow(12, 42)

This returns 42. A general if expression needs both branches and a compatible result type. Only the selected branch executes, and bindings introduced in it stay within that branch. A non-final validation guard may omit else when its body ends in an explicit fail.

Combine conditions

kalandra
fn main() -> int:
  value = 42
  valid = value >= 0 and value < 100
  if valid and not false:
    value
  else:
    0

This also returns 42. Write separate comparisons joined with and. Ungrouped chained comparisons are rejected. Floating-point comparisons follow the numerical rules, including NaN behavior; Boolean arrays and numerical tensors remain distinct types.

Update local state in a loop

Numeric and Bool scalars can be reassigned without a mut declaration. Numeric and Bool array bindings can also be reassigned in CPU functions, including across task waits and expected-error handling. Their lengths may change; their element type stays fixed. A while statement reevaluates its Bool condition before each iteration. It may run zero times. In statement form, supply the function result separately after the loop. To obtain a value from a loop, use the expression form described below.

kalandra
fn main() -> int:
  index = 0
  total = 0
  while index < 5:
    if index < 3:
      total = total + index
    else:
      total = total + 10
    index = index + 1
  total

This returns 23. A statement if may omit else; a value-producing if still needs both branches. Assignment to an existing scalar changes that binding; a new name introduced in a branch or iteration stays in that scope. Reassignment preserves the scalar type. Checked numeric/Bool array element writes are described in the array tutorial. break exits the nearest loop; continue skips the rest of its body and starts the next iteration.

Iterate over an array

for visits array elements in index order. It evaluates the array expression once and keeps that storage alive until iteration finishes. Replacing the original binding does not change which array is visited. Writes to writable elements remain visible to subsequent reads. The iteration binding is local to the body and does not replace an outer binding with the same name.

kalandra
fn main() -> int:
  total = 0
  for value in [1, 2, 3, 4]:
    if value == 2:
      continue
    total = total + value
  total

This returns 8. continue advances to the next element. A bare break would end this statement loop immediately. for accepts CPU arrays, including arrays of Bool, Strings, records and function values. Use an explicit conversion to an array when starting from a numerical tensor.

Iterate over an integer range

Use range(stop), range(start, stop), or range(start, stop, step) as a for iterable. These forms are allocation-free loop syntax; they do not construct an array or a reusable iterator value. Arguments are completed int values, evaluated once from left to right. The default start is zero and the default step is one. The stop is exclusive. A negative step counts downward; a direction that cannot reach the stop produces no iterations. A zero step produces a checked arithmetic fault, including for an otherwise empty range. Advancing beyond the int limits ends the range without wrapping around.

kalandra
fn main() -> int:
  total = 0
  for index in range(5, -2, -2):
    total = total + index
  total

This returns 8, from 5 + 3 + 1 - 1. Reassigning index inside the body changes that iteration's local value; it does not change the next range value.

Build an array with yield

A for or while expression containing yield builds an array. Each executed yield appends one value in execution order. You can filter values with if, skip an iteration with continue, or stop with a bare break. Values yielded before continue or break remain in the result. A collecting loop has no else result and does not accept break value.

kalandra
fn main() -> [int]:
  squares = for value in [3, -2, 7, 0]:
    if value > 0:
      yield value * value
  squares

This returns [9, 49]. All yielded values must have the same element type. A type annotation on the result supplies the element type for numeric literals. If no yield executes, the result is an empty array of that type. The loop runs eagerly before the assignment completes; yield does not suspend execution or create a lazy generator.

Nested statement loops contribute to the enclosing collecting expression. This lets one traversal produce zero, one or several values per iteration.

kalandra
fn main() -> [int]:
  results = for row in range(4):
    for value in range(row):
      if value % 2 == 0:
        yield row * 10 + value
  results

This returns [10, 20, 30, 32]. break and continue still target the nearest loop. Assigning a nested loop expression to a separate binding creates a separate result array; its yields do not append to the outer result. A function or callback cannot yield into its caller's loop.

kalandra
fn main() -> [int]:
  index = 0
  values = while index < 6:
    index = index + 1
    if index == 2:
      continue
    yield index
    if index == 4:
      break
    yield index * 10
  values

This returns [1, 10, 3, 30, 4]. yield accepts the supported array element types: numeric scalars, Bool, Strings, records and functions. Array and tensor elements are not supported. Values must be completed and on the CPU; await tasks and handle expected errors before yielding their results. Yielding does not settle other tasks created in the iteration. Cancellation, faults and resource exhaustion abort construction without returning a partial array.

The result has fresh array storage. Owned elements retain their ordinary aliasing rules: yielding a record does not duplicate arrays referenced by its fields. Existing read locks and mutation permissions still apply. Use generate for an indexed construction with a known count, map to transform an existing array, or a collecting loop when filtering, nested traversal or early exits make the construction clearer.

Get a value from a loop

For a single result rather than a yielded array, both while and for support an else suite that supplies its result when iteration finishes normally, including when the body never runs. break value ends the nearest loop and supplies its result instead; that path skips else. Every break and the else result must have compatible types. There is no implicit last-iteration result, and continue never supplies a value.

kalandra
fn main() -> int:
  for value in [3, 7, 9]:
    if value > 5:
      break value
  else:
    0

This returns 7. The same search over an empty array would return 0. Statement and collecting loops use bare break; a single-result expression loop uses break value.

kalandra
fn main() -> int:
  index = 0
  result = while index < 10:
    index = index + 1
    if index == 4:
      break index * 10
  else:
    7
  result + index

This returns 44. The else suite runs only after a false condition or exhausted iterable, never after break, a propagated error, a checked fault or cancellation. Loop expressions may return any supported completed value, including arrays, Strings, records, functions and tensors. Existing type, ownership and residency rules still apply.

Exit safely around tasks

break and continue target the nearest enclosing loop in the same function. A closure cannot jump into its caller's loop. Statements after an unconditional loop transfer are rejected as unreachable.

Tasks created in an iteration must settle before every backedge, break and continue. Await every relevant child before the exit; a cancellation request alone does not establish settlement. Exiting a completed nested parallel group closes and releases its scheduler records before control transfers. Existing error and checked-fault precedence applies to that cleanup.

A task started before a loop may still be running afterward: the loop may execute zero times or break before an await. Await it outside the loop before mutating storage it reads. Cancellation and resource limits apply to all loop forms. For long loops that start tasks, use a nested parallel block for each batch so completed groups release scheduler records between iterations.

Match a declared variant

Use match to select an enum or error alternative. Cover every alternative or use a final _ wildcard branch. Names bound to a variant's fields exist only in the selected branch. Match guards and nested destructuring are not supported.