Binding and Extraction
Stone has two assignment operators. Simple assignment (=) binds a value to a name. Extracting assignment (:=) extracts fields from a structured value by name.
Important: Stone has no reassignment and no shadowing. Once a name is bound, it cannot be rebound in any scope—including inner scopes. You cannot shadow a name from an outer scope. If you need a different value, use a different name. Function parameters are bindings and cannot be rebound within the function body.
Simple Assignment
The = operator binds a value to a name:
x = 5
name = "hello"
point = { x = 10, y = 20}
When assigning an object, you access fields with dot notation:
result = evolve { sum = 0, count = 0 } for (x in xs) {
sum' = sum + x
count' = count + 1
}
print(result.sum)
print(result.count)
Extracting Assignment
The := operator extracts fields from an object by name:
sum, count := evolve { sum = 0, count = 0 } for (x in xs) {
sum' = sum + x
count' = count + 1
}
print(sum)
print(count)
The names on the left side must match field names in the object. This is name-based extraction, not positional.
:= is only valid when the right-hand side is an object value. The left-hand names (or aliases) must correspond to its fields.
Partial Extraction
You don't need to extract all fields. Take only what you need:
point = { x = 10, y = 20, z = 30}
x, y := point
// x is 10, y is 20
// z is not bound
Single-field extraction:
sum := evolve { sum = 0, count = 0 } for (x in xs) {
sum' = sum + x
count' = count + 1
}
// only sum is bound
Single-Name Extraction
When the left-hand side of := is a single name, it still obeys the extraction rule. This means you can only use name := expr if the expression returns an object with a field named name, and you're extracting that specific field.
For example, this is valid because loop returns an object with a sum field:
sum := evolve { sum = 0, count = 0 } for (x in xs) {
sum' = sum + x
count' = count + 1
}
// 'sum' is bound to the value of the 'sum' field from the loop's result.
However, if you try to extract a result where the single name on the LHS does not match a field in the RHS object, it will result in an error. For instance:
fn divide_with_remainder(a, b) = { quotient = a / b, remainder = a % b }
res := divide_with_remainder(a, b) // ERROR: no field named 'res' in the returned object
In this case, divide_with_remainder returns an object with fields quotient and remainder, but not res. Therefore, the extraction fails.
If you want to bind the entire object returned by an expression to a single name, you should use the plain assignment operator =:
fn divide_with_remainder(a, b) = { quotient = a / b, remainder = a % b }
result = divide_with_remainder(a, b) // Valid: 'result' is bound to the entire object
print(result.quotient)
print(result.remainder)
Name Matching
Extraction matches by name, not position. The order on the left side doesn't matter:
point = { x = 10, y = 20}
x, y := point // x is 10, y is 20
y, x := point // x is 10, y is 20 (same result)
If a name doesn't exist in the object, it's an error:
point = { x = 10, y = 20}
x, z := point // ERROR: no field 'z' in point
Aliases
Use as to bind a field to a different name:
point = { x = 10, y = 20}
x as horizontal, y as vertical := point
// horizontal is 10, vertical is 20
This is useful when field names would conflict with existing variables or when you want more descriptive names:
sum as total, count as n := evolve { sum = 0, count = 0 } for (x in xs) {
sum' = sum + x
count' = count + 1
}
mean = total / n
Without aliases, the names must match exactly:
total, n := evolve { sum = 0, count = 0 } for (x in xs) {
sum' = sum + x
count' = count + 1
}
// ERROR: no fields named 'total' or 'n'
Extracting from Objects
Any object can be extracted from:
person = { name = "Alice", age = 30, city = "Paris"}
name, age := person
print(name) // "Alice"
print(age) // 30
Nested objects require multiple extraction steps:
data = {
user: { name = "Bob", id = 42},
score: 100
}
user, score := data
name, id := user
print(name) // "Bob"
Extracting Function Results
Functions that return objects can be extracted:
fn divide_with_remainder(a, b) = { quotient = a / b, remainder = a % b }
quotient, remainder := divide_with_remainder(17, 5)
print(quotient) // 3
print(remainder) // 2
Extracting Loop Results
The most common use of extraction is with loop results:
min, max := evolve { min = xs[0], max = xs[0] } for (x in xs) {
if (x < min) {
min' = x
}
if (x > max) {
max' = x
}
}
The loop returns { min: ..., max: ... } and := extracts both fields.
Examples
Swapping via object:
pair = { a = 1, b = 2}
b as first, a as second := pair
// first is 2, second is 1
Extracting statistics:
sum, count, max := evolve { sum = 0, count = 0, max = xs[0] } for (x in xs) {
sum' = sum + x
count' = count + 1
if (x > max) {
max' = x
}
}
mean = sum / count
print(mean)
print(max)
Working with coordinates:
start = { x = 0, y = 0}
end = { x = 10, y = 20}
x as x1, y as y1 := start
x as x2, y as y2 := end
distance = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
Partial extraction from a large object:
config = {
host: "localhost",
port: 8080,
timeout: 30,
retries: 3,
debug: false
}
host, port := config
// only extract what you need