Variables
Three binding forms, reactive bindings, contracts, and destructuring.
Summary
let is immutable (reassignment is a runtime error), var is mutable, and const is identical to let at runtime but signals intent. All three accept optional type annotations. bind creates a reactive binding that recomputes automatically when its dependencies change; cascading is supported.where clauses add runtime-checked contracts to any binding or function parameter. Array, tuple, and struct destructuring work in any binding position.
Canonical
let x = 42 -- immutable binding
var y = "hello" -- mutable binding (can reassign)
const MAX = 100 -- constant (same as let at runtime, signals intent)
-- with type annotations
let count: int = 42
var name: str = "XS"let bindings cannot be reassigned: that's a runtime error. var bindings can be reassigned with =.
const is identical to let at runtime.
Reactive Bindings
bind creates a variable that automatically recomputes when its dependencies change.
var price = 10
var qty = 3
bind total = price * qty
println(total) -- 30
price = 20
println(total) -- 60 (auto-updated)
qty = 5
println(total) -- 100
-- bindings can depend on other bindings (cascading)
bind doubled = total * 2
println(doubled) -- 200
price = 1
println(total) -- 5
println(doubled) -- 10
-- works with strings too
var name = "world"
bind greeting = "hello " ++ name
println(greeting) -- hello world
name = "xs"
println(greeting) -- hello xsbind tracks which variables are read when the expression is first evaluated. When any of those variables are reassigned, the binding automatically recomputes. Cascading works: if binding A depends on binding B, and B's dependency changes, both B and A update in order.
Reactivity is wired through the interpreter, the VM, and the JIT; all three replay the bound expression on dependency change. Transpiler targets (--emit js, --emit c, --emit wasm) lower bind as a regular let since static targets can't observe variable mutation through the same hook.
Contracts (where clauses)
Add where after a type annotation to enforce a condition on the value. The condition is checked at runtime.
let age: int where age > 0 and age < 150 = 25
let name: str where name.len() > 0 = "xs"
let score: int where score >= 0 and score <= 100 = 85If the condition fails, a catchable error is thrown:
try {
let bad: int where bad > 100 = 5
} catch e {
println(e) -- contract violation
}Contracts work on function parameters too:
fn divide(a: int, b: int where b != 0) {
return a / b
}
println(divide(10, 2)) -- 5
divide(10, 0) -- throws: contract violationContracts are gradual: no where clause means no checking. Add them where you want enforcement. They're checked at runtime in the interpreter. In the VM and transpilers, contracts on function params are not yet enforced (variables are).
Destructuring
-- array destructuring
let [a, b, c] = [1, 2, 3]
println(a) -- 1
-- tuple destructuring
let (x, y) = (10, 20)
println(x) -- 10
-- nested tuple destructuring
let (a, (b, c)) = (1, (2, 3))
println(c) -- 3
-- struct destructuring
struct Point { x, y }
let Point { x: px, y: py } = Point { x: 100, y: 200 }
println(px) -- 100Array destructuring requires an exact length match.