Skip to content
kata / a language workbench

Bindings and scope

let introduces a new binding. Assignment changes an existing binding.

let count = 0
count = count + 1
print(count) # 1

There is no mut modifier. Bindings created with let can be reassigned, and const is not implemented.

let count: Int = 3
let label: Str = "ready"

The interpreter checks each initializer against its annotation. In the current implementation, the annotation is then discarded: it does not prevent subsequent assignment of a different type. Treat it as an assertion about initialization, not a persistent binding contract. Function parameters, function returns, and kind fields have their own runtime checks.

A name is visible in its defining scope and nested scopes.

let label = "outside"
with {
let label = "inside"
print(label)
}
print(label)

This prints inside, then outside. The inner let shadows the outer name. By contrast, an assignment without let searches for and updates an existing binding:

let total = 1
with {
total = total + 4
}
print(total) # 5

The difference matters for closures: reassignment changes the binding a closure already captured; a new let creates a new binding.

let (name, score) = ("Ada", 42)
let (_, (x, y)) = ("point", (3, 4))
print("{name}: {score}")
print(x + y)

A tuple pattern names its parts, and _ discards a part. Patterns can nest. A binding name cannot appear twice in the same pattern.

let accepts irrefutable patterns: bindings, wildcards, and tuples of those patterns. Use match when a pattern might not fit, such as an enum variant or a literal.

Do not read let second = first as a promise of deep copying. Collection aliases currently share backing storage in ways that can become invalid after growth; see collections.