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]