Loops

Loops in Stone are expressions that evolve state across iterations using a state transition model. Unlike imperative loops that mutate variables in place, a Stone loop uses the evolve keyword to declare its state upfront, and the loop body explicitly declares next-state values using the prime (') suffix.

State Transition Model: Each state variable v has two values during iteration:

  • v — the current iteration's value (immutable, read-only)
  • v' — the next iteration's value (write-only, assigned by you)

At the end of each iteration, the state transition occurs: v := v' for all state variables. All right-hand sides evaluate against the current state before any transitions occur. The prime notation makes state transitions explicit and composable—you can reference v' on the right-hand side of other assignments within the same iteration.

Edge Case Rules

Default next-state: If a state variable v' is not assigned in an iteration, it implicitly becomes v (state carries forward unchanged).

Single-assignment: Assigning v' more than once in the same iteration is an error. Each state variable can only have one next-state assignment per iteration.

Break semantics: When break is executed, the loop returns the staged v' values (if assigned in that iteration), otherwise the current v values. This means break "commits what you have so far."

Referencing v': The next-state value v' is only defined after it is assigned. Referencing v' before its assignment in that iteration is an error.

The evolve Keyword

The evolve keyword explicitly marks a loop as stateful. It comes in two forms:

Single state — uses (), returns the value directly:

total = evolve (sum = 0) for (x in xs) { sum' = sum + x }
// total is 15 (a number, not a record)

Multi state — uses {}, returns a record:

result = evolve { sum = 0, count = 0 } for (x in xs) {
    sum' = sum + x
    count' = count + 1
}
// result is {sum = 15, count = 5}

The () vs {} distinction determines the return type:

  • evolve (x = 0) — single state variable, returns the value directly
  • evolve { x = 0, y = 0 } — multiple state variables, returns a record

Basic Structure

A loop expression has three parts: the evolve declaration, a driver (for or while), and a body.

result = evolve (x = 1) while (x < 100) {
    x' = x * 2
}

The state variable x doubles each iteration, starting from 1. The assignment x' = x * 2 declares the next iteration's value. When the while condition becomes false, the loop exits and the final value is assigned to result.

State Initialization

State variables are declared in the evolve clause. They must be initialized:

evolve (total = 0)
evolve { min = xs[0], max = xs[0] }
evolve { found = false, index = 0 }

You can also include type annotations:

evolve (count: num = 0)
evolve { items: array<num,1> = [] }

These variables are only accessible within the loop and in the result.

Loop Body

The body contains assignments that declare state transitions using the prime (') suffix. Each iteration sees the state from the previous iteration, and prime assignments specify the next iteration's state:

sum = evolve (sum = 0) for (x in xs) {
    sum' = sum + x
}

Key Rule: Inside a loop body, you must use v' = expr to assign next-state values. Plain v = expr is not allowed for state variables.

On first iteration, sum is 0. If x is 5, the assignment sum' = sum + x declares that next iteration's sum will be 5. On second iteration, if x is 3, sum' = sum + 3 sets next iteration's sum to 8. The current sum value remains immutable during each iteration.

Composability: You can reference v' on the right-hand side of other assignments:

sum, total := evolve { sum = 0, total = 0 } for (x in xs) {
    sum'   = sum + x
    total' = total + sum'  // uses the next-state value of sum
}

Loop Result

A multi-state loop returns an object containing the final values of all state variables:

result = evolve { sum = 0, count = 0 } for (x in xs) {
    sum'   = sum + x
    count' = count + 1
}
print(result.sum)
print(result.count)

A single-state loop returns the value directly:

total = evolve (sum = 0) for (x in xs) {
    sum' = sum + x
}
print(total)  // prints the number directly

You can extract multi-state results to get fields directly. Extraction with := matches by name—the names on the left must match field names in the result object:

sum, count := evolve { sum = 0, count = 0 } for (x in xs) {
    sum'   = sum + x
    count' = count + 1
}
print(sum)
print(count)

If you only need some fields, extract just those:

sum := evolve { sum = 0, count = 0 } for (x in xs) {
    sum'   = sum + x
    count' = count + 1
}
// only sum is bound; count is discarded

Use aliases when you want different names in scope:

total, n := evolve { sum = 0, count = 0 } for (x in xs) {
    sum'   = sum + x
    count' = count + 1
}  // ERROR: no fields named 'total' or 'n'

sum as total, count as n := evolve { sum = 0, count = 0 } for (x in xs) {
    sum'   = sum + x
    count' = count + 1
}  // CORRECT: extracts sum as 'total', count as 'n'

Drivers

Drivers control when and how a loop iterates.

The While Driver

The while driver continues iteration as long as a condition is true:

x = evolve (x = 1) while (x < 100) {
    x' = x * 2
}
// x is 128 (first power of 2 >= 100)

The condition is checked before each iteration. When it becomes false, the loop exits and returns the current state.

// Newton's method for square root
guess = evolve (guess = n / 2) while (abs(guess * guess - n) > 0.0001) {
    guess' = (guess + n / guess) / 2
}

The For Driver

The for driver iterates over each element in a collection:

for (x in xs)
for (item in items)
for (row in matrix)

The iteration variable (x, item, row) is available in the loop body but does not appear in the result.

sum = evolve (sum = 0) for (x in xs) {
    sum' = sum + x
}

Early Exit with Break

The break statement exits a loop immediately, returning the current state. Important: break exits only the innermost loop. In nested loops, each break only affects the loop it's directly inside.

// Find first negative value
found, value := evolve { found = false, value = 0 } for (x in xs) {
    if (x < 0) {
        found' = true
        value' = x
        break
    }
}

For while (true) loops, break is the only way to exit:

// Read until empty input
lines = evolve (lines = []) while (true) {
    line = read_line()
    if (line == "") {
        break
    }
    lines' = append(lines, line)
}

Signaling Break Conditions to Outer Loops

When you need an inner loop to signal a break condition to an outer loop, use result objects:

found = evolve (found = false) for (row in matrix) {
    inner_found = evolve (inner_found = false) for (x in row) {
        if (x == target) {
            inner_found' = true
            break
        }
    }
    if (inner_found) {
        found' = true
        break
    }
}

Conditional Updates

Use conditionals inside the loop body to update state selectively:

positive_sum = evolve (positive_sum = 0) for (x in xs) {
    if (x > 0) {
        positive_sum' = positive_sum + x
    }
}

When the condition is false, the state variable retains its value from the previous iteration.

Examples

Finding the maximum value:

max = evolve (max = xs[0]) for (x in xs) {
    if (x > max) {
        max' = x
    }
}

Counting occurrences:

count = evolve (count = 0) for (x in xs) {
    if (x == target) {
        count' = count + 1
    }
}

Computing mean:

sum, count := evolve { sum = 0, count = 0 } for (x in xs) {
    sum'   = sum + x
    count' = count + 1
}
mean = sum / count

Fibonacci up to a limit (demonstrating state transition semantics):

a, b := evolve { a = 0, b = 1 } while (b < 1000) {
    a' = b
    b' = a + b
}
// a is the largest Fibonacci number below 1000

Note: Both a' = b and b' = a + b evaluate their right-hand sides using the current iteration's state before any transitions occur. The right-hand side of b' = a + b sees the current a (not the next-state value being assigned in the same iteration).

Searching with early exit:

idx, found := evolve { idx = -1, found = false, i = 0 } for (x in xs) {
    if (x == target) {
        idx'   = i
        found' = true
        break
    }
    i' = i + 1
}
// found is false if not found, idx is -1
// found is true if found, idx is the position