Syntax Basics
Functions
Section titled “Functions”Functions are defined with fn and use Hindley-Milner type inference:
fn add(x: Int, y: Int) -> Int { x + y}
// Type annotations are optional when inferablefn double(x) { x * 2}
// Single-expression functionsfn square(x: Int) -> Int = x * xLet Bindings
Section titled “Let Bindings”let introduces immutable bindings. The type is inferred unless annotated:
let x = 42let y: Int = x + 8let pair = (x, y) // Tuple: (Int, Int)Control Flow
Section titled “Control Flow”If Expressions
Section titled “If Expressions”if is an expression — it returns a value:
let status = if x > 0 { "positive" } else { "non-positive" }Match Expressions
Section titled “Match Expressions”Pattern matching on variants, records, and primitives:
type Option[T] = Some(T) | None
fn unwrap_or(o: Option[Int], default: Int) -> Int { match o { Some(v) => v, None => default }}Match arms can have guards:
match value { n if n > 0 => "positive", n if n < 0 => "negative", _ => "zero"}Records
Section titled “Records”Records are row-polymorphic — a function can accept any record with the fields it needs:
fn full_name(r: { first: String, last: String }) -> String { r.first + " " + r.last}
let person = { first: "Alice", last: "Smith", age: 30 }full_name(person) // "Alice Smith" — extra fields are fineRecords are structurally typed and created with { field: value, ... } syntax.
Variants
Section titled “Variants”Algebraic data types defined with type ... = ... | ...:
type Result[T, E] = Ok(T) | Err(E)
type Tree[T] = Leaf(T) | Node(Tree[T], Tree[T])Construct and match:
let ok = Ok(42)
match ok { Ok(v) => "Got " + Int.to_string(v), Err(e) => "Error: " + e}Arrays
Section titled “Arrays”let nums = [1, 2, 3, 4, 5]let first = nums[0] // Index accesslet len = length(nums) // Built-in lengthPipe Operator
Section titled “Pipe Operator”The |> operator pipes a value left-to-right into a function:
let inc = fn(x) { x + 1 } inlet dbl = fn(x) { x * 2 } in1 |> inc |> dbl // 4x |> f is equivalent to f(x). Chaining a |> f |> g |> h applies f, then g, then h in order.
Send Operators
Section titled “Send Operators”There are two syntaxes for sending a message to an actor:
// Keyword formsend counter inc()
// Operator form (equivalent)counter ! inc()Both parse to the same AST. The ! form is more concise; the send form is more readable for complex arguments:
w ! watch(v)send counter inc_by(5)send counter get(self)Ask Operator
Section titled “Ask Operator”ask is a synchronous request/reply call to an agent or actor behavior. The caller blocks until the target responds:
let a = spawn Assistant {} inask a ask("What is an actor model?")See AI Agents for agent declarations and tool binding.
Ternary Expressions
Section titled “Ternary Expressions”if can be used inline with the then keyword:
let fib = fn(n) { if n <= 1 then n else fib(n - 1) + fib(n - 2)} in fib(10)if cond then a else b returns a when cond is truthy, b otherwise. The block form (if cond { a } else { b }) is equivalent.
Effect Annotations
Section titled “Effect Annotations”Function signatures declare their effects with ! or throws followed by an effect row:
// Pure function — no effectsfn add(x: Int, y: Int) -> Int = x + y
// Effectful — performs IOfn greet(): ! IO Unit { perform IO.print("Hello")}
// throws is an alias for !fn log(msg: String): throws IO Unit { perform IO.print(msg)}See Algebraic Effects for the full effect system.
Comments
Section titled “Comments”// Single-line comment
//// Regular comment (not a doc comment)
/// Doc comment — attaches to the next declaration
//! Module-level doc comment