Indexed Mapping

Indexed mapping is Stone's mechanism for element-wise parallel transformations. When an assignment includes indexed variables on the left side, Stone automatically iterates over the index domain.

Basic Mapping

A mapping is triggered when the left side of an assignment has an index variable:

out[i] = f(xs[i])

Stone infers iteration over all valid values of i based on the length of xs. This is equivalent to creating an array where each element is the result of applying f to the corresponding element of xs.

Mappings are always total over the inferred index domain: each index in the domain produces exactly one output element.

xs = [1, 2, 3, 4, 5]
doubled[i] = xs[i] * 2
// doubled is [2, 4, 6, 8, 10]

Index Inference

Stone determines the iteration range from indexed expressions on the right side:

squares[i] = xs[i] * xs[i]
// i ranges from 0 to len(xs) - 1

The index variable can be any identifier:

out[j] = xs[j] + 1
out[idx] = xs[idx] * 2
out[n] = xs[n] / total

Index Inference Rules

Index domain: If the right-hand side mentions xs[i], the domain for i is 0..len(xs)-1.

Index must appear on RHS: If the left-hand side uses out[i] but the right-hand side doesn't mention any indexed expression using i, it is an error. The index must be inferrable from the RHS.

// ERROR: index 'i' not used on RHS
result[i] = 42

Same iterator, equal lengths: When multiple arrays use the same index variable, they must have equal lengths (runtime error otherwise).

// xs and ys must have the same length
sums[i] = xs[i] + ys[i]

Independent indices (cartesian product): If the RHS uses different index variables like xs[i] and ys[j], this produces an N-dimensional cartesian product mapping. The output shape matches the number of independent indices.

// Creates a 2D array of shape [len(xs), len(ys)]
products[i][j] = xs[i] * ys[j]

// Creates a 3D array for three independent indices
cube[i][j][k] = as[i] + bs[j] + cs[k]

Multiple Arrays

When multiple arrays share the same index, they must have the same length:

xs = [1, 2, 3]
ys = [10, 20, 30]

sums[i] = xs[i] + ys[i]
// sums is [11, 22, 33]

Length mismatch is an error:

xs = [1, 2, 3]
ys = [10, 20]

sums[i] = xs[i] + ys[i]
// ERROR: xs has 3 elements, ys has 2

Constant Participation

Values without indices are treated as constants across all iterations:

xs = [1, 2, 3, 4]
bias = 10

adjusted[i] = xs[i] + bias
// adjusted is [11, 12, 13, 14]

The constant participates in every iteration:

factor = 2.5
scaled[i] = xs[i] * factor
// each element multiplied by 2.5

Multi-Dimensional Mapping

Use multiple index variables for nested arrays:

matrix = [
    [1, 2, 3],
    [4, 5, 6]
]

doubled[i][j] = matrix[i][j] * 2
// doubled is [[2, 4, 6], [8, 10, 12]]

Each index iterates over its corresponding dimension:

transposed[j][i] = matrix[i][j]
// rows become columns

Mixed Dimensionality

You can combine arrays of different depths if their shared indices have matching lengths:

matrix = [
    [10, 20, 30],
    [40, 50, 60]
]
row_offsets = [1, 2]

adjusted[i][j] = matrix[i][j] + row_offsets[i]
// adjusted is [[11, 21, 31], [42, 52, 62]]

Here i indexes both matrix (rows) and row_offsets. Both must have length 2. The j index only applies to columns within each row.

Another example—normalizing rows by their sum:

row_sums = [60, 150]  // precomputed

normalized[i][j] = matrix[i][j] / row_sums[i]

Calling Functions

Functions are called for each iteration:

fn square(x) = x * x

xs = [1, 2, 3, 4, 5]
squares[i] = square(xs[i])
// squares is [1, 4, 9, 16, 25]

Functions with multiple arguments:

fn add(a, b) = a + b

result[i] = add(xs[i], ys[i])

Chaining Mappings

The result of one mapping can feed into another:

xs = [1, 2, 3, 4]

doubled[i] = xs[i] * 2
// doubled is [2, 4, 6, 8]

plus_one[i] = doubled[i] + 1
// plus_one is [3, 5, 7, 9]

Literal Indices

Literal indices do not trigger mapping:

first = xs[0]      // single element access
last = xs[len(xs) - 1]

// Mixed: mapping with a constant index
column[i] = matrix[i][0]
// extracts the first column

Mapping vs Loops

Use mapping for element-wise transformations where outputs depend only on corresponding inputs:

// Good use of mapping
doubled[i] = xs[i] * 2
distances[i] = sqrt(points[i].x * points[i].x + points[i].y * points[i].y)

Use loops when you need accumulation or when an output depends on multiple inputs:

// Must use a loop - accumulating a sum
sum = evolve (sum = 0) for (x in xs) {
    sum' = sum + x
}

// Must use a loop - each output depends on previous
running[i] = ???  // can't express running total with pure mapping

Examples

Scaling a vector:

vec = [3, 4]
scale = 2
scaled[i] = vec[i] * scale
// scaled is [6, 8]

Element-wise operations on two arrays:

a = [1, 2, 3]
b = [4, 5, 6]

sum[i] = a[i] + b[i]       // [5, 7, 9]
product[i] = a[i] * b[i]   // [4, 10, 18]
diff[i] = a[i] - b[i]      // [-3, -3, -3]

Applying a threshold:

fn clamp(x, lo, hi) = if (x < lo) { lo } elif (x > hi) { hi } else { x }

values = [5, 15, 25, 35]
clamped[i] = clamp(values[i], 10, 30)
// clamped is [10, 15, 25, 30]

Extracting a field from objects:

points = [
    { x = 0, y = 0},
    { x = 3, y = 4},
    { x = 6, y = 8}
]

xs[i] = points[i].x
// xs is [0, 3, 6]

Matrix operations:

matrix = [
    [1, 2],
    [3, 4]
]

// Add scalar to all elements
plus_ten[i][j] = matrix[i][j] + 10

// Element-wise multiplication with another matrix
other = [[2, 0], [0, 2]]
product[i][j] = matrix[i][j] * other[i][j]

Computing distances from origin:

fn distance(x, y) = sqrt(x * x + y * y)

points = [
    { x = 3, y = 4},
    { x = 5, y = 12},
    { x = 8, y = 15}
]

distances[i] = distance(points[i].x, points[i].y)
// distances is [5, 13, 17]