Skip to content

Language reference

Syntax and values

Source files, indentation, bindings, calls and function results.

Source files use the .k extension. Indentation defines a block of statements and expressions. Use spaces consistently; the formatter uses two spaces per level. A colon introduces a function or control-flow body. The final expression supplies a function's result. if, while and for support value-producing forms as well as statements; a loop expression can collect values with yield, or select a single result with else and break value.

Bindings and results

kalandra
fn main() -> int:
  width = 6
  height: int = 7
  area = width * height
  area

This program returns 42. Numeric and Bool scalar bindings can be reassigned while preserving their type. An annotation such as height: int declares the expected type; otherwise the checker infers a type. A bare whole-number literal normally infers int, while a decimal or exponent literal normally infers float. An expected type can give a literal a narrower numeric type.

Comments and multiline expressions

Use # for line comments. Blank lines and comment-only lines do not change block structure. Parentheses and collection delimiters permit multiline expressions:

kalandra
fn main() -> int:
  # The final expression is the result.
  total = (
    20 +
    22
  )
  total

This program also returns 42. Identifiers use ASCII letters and underscores, followed by letters, digits or underscores. String literals and comments can contain Unicode. Tabs outside comments and strings are diagnosed.

Calls and pipelines

kalandra
fn add(left: int, right: int) -> int:
  left + right

fn main() -> int:
  40 -> add(2)

The program returns 42. The pipeline inserts its left value as the first argument. Here the final line is equivalent to add(40, 2). Use parentheses when a larger expression needs to be one argument.

Reassign a local scalar

Reuse = to update a numeric or Bool binding while preserving its type. A scalar function parameter is a private value; rebinding it cannot change the caller's binding. Assignments in a statement branch or loop update an existing scalar name; a new name introduced inside that body stays local to it. CPU functions also support replacing numeric and Bool array bindings. String, record and function-array bindings, records, function values and tensors cannot be reassigned.

kalandra
fn double(value: int) -> int:
  value = value * 2
  value

fn main() -> int:
  amount = 10
  amount = double(amount)
  amount

This returns 20. double changes its private parameter; main changes amount only when it assigns the returned value.

Automatic lifetimes

You do not manually free arrays, tensors, Strings or captured values. Kalandra keeps them alive while they are needed and releases their storage automatically. Passing or returning a value does not necessarily copy its storage: arrays can have aliases, and tensor operations preserve their inputs. Read access and mutation rules still apply when storage is shared.