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