Functions
Functions in Stone are defined with the fn keyword. They can be single-expression functions for simple transformations or block functions for more complex logic.
Important binding rules:
- Function parameters are bindings and cannot be rebound within the function body
- No reassignment exists within function bodies—if you need a different value, bind it to a new name
- The last expression in a block function is its return value
return(expr)is only for early exit—it exits the innermost function. Note:returnwithout parentheses is not valid.
Single-Expression Functions
For simple functions, use the = syntax:
fn add(a, b) = a + b
fn square(x) = x * x
fn double(x) = x * 2
The expression after = is the return value. No return keyword is needed.
import sqrt from math
fn distance(x, y) = sqrt(x * x + y * y)
fn average(a, b) = (a + b) / 2
fn greet(name) = "Hello, " + name
Block Functions
For functions needing intermediate bindings, use braces. The last expression in the block is the return value:
import sqrt from math
fn hypotenuse(a, b) {
a_sq = a * a
b_sq = b * b
sqrt(a_sq + b_sq)
}
For conditional logic, prefer expression functions with if/elif/else:
fn abs(x) = if (x >= 0) { x } else { -x }
fn clamp(x, lo, hi) = if (x < lo) { lo } elif (x > hi) { hi } else { x }
Multiple Statements
Block functions can contain any number of bindings. The last expression is the return value:
fn process(x) {
doubled = x * 2
adjusted = doubled + 1
adjusted
}
Intermediate values are local to the function. Remember: parameters cannot be rebound, so you must create new bindings:
// ERROR: Cannot rebind parameter
fn bad_example(x) {
x = x * 2 // ERROR: x is a parameter binding
x
}
// CORRECT: Bind to a new name
fn good_example(x) {
doubled = x * 2
doubled
}
Calling Functions
Call functions by name with arguments in parentheses:
result = add(3, 4) // 7
dist = distance(3, 4) // 5
msg = greet("Alice") // "Hello, Alice"
Functions can call other functions:
fn square(x) = x * x
fn sum_of_squares(a, b) = square(a) + square(b)
result = sum_of_squares(3, 4) // 25
Keyword Arguments
When calling a function, you can pass arguments by name instead of position. Named arguments are matched to parameter names and reordered at compile time:
fn greet(name, greeting = "Hello") = greeting + ", " + name
greet("Alice") // positional — "Hello, Alice"
greet(name = "Alice") // named — "Hello, Alice"
greet(name = "Alice", greeting = "Hi") // both named — "Hi, Alice"
greet("Alice", greeting = "Hi") // positional then named — "Hi, Alice"
This is especially useful when a function has many optional parameters. You can skip defaults and name only what you need:
fn bdf(f, y0, t, max_order = 5, tol = 1e-6, max_newton = 10)
bdf(f, y0, t, tol = 1e-9) // skip max_order, override tol
bdf(f, y0, t, max_newton = 50) // skip max_order and tol
Keyword arguments work with the pipe operator. The pipe fills the first positional slot, and you can name the rest:
fn smooth(data, window = 5, method = "gaussian") { ... }
signal |> smooth(window = 10) // signal goes to data
Rules:
- Positional arguments must come before named arguments.
foo(name = 1, x)is a compile error. - Function signatures are always positional — there is no keyword-only parameter syntax.
- Backends see purely positional calls; named arguments are resolved at compile time.
Functions with Objects
Functions can accept and return objects. Stone uses width subtyping — a record with extra fields satisfies a type that requires fewer fields:
fn make_point(x, y) = { x = x, y = y}
fn translate(point, dx, dy) = {
x = point.x + dx,
y = point.y + dy
}
p = make_point(10, 20)
moved = translate(p, 5, -5)
// moved is {x = 15, y = 15}
When a function has type-annotated record parameters, extra fields are allowed but missing required fields are an error:
fn get_x(obj: {x: num}) = obj.x
get_x({x = 1, y = 2}) // OK — extra field y is fine
get_x({y = 2}) // ERROR: Missing field: x
Extraction works with function results:
fn min_max(xs) {
min, max := evolve { min = xs[0], max = xs[0] } for (x in xs) {
if (x < min) {
min' = x
}
if (x > max) {
max' = x
}
}
{ min = min, max = max }
}
min, max := min_max([3, 1, 4, 1, 5, 9])
Functions with Loops
Functions can contain loops:
fn sum(xs) {
total = evolve (total = 0) for (x in xs) {
total' = total + x
}
total
}
fn count_positive(xs) {
count = evolve (count = 0) for (x in xs) {
if (x > 0) {
count' = count + 1
}
}
count
}
Functions in Mapping
Functions are commonly used with indexed mapping:
fn square(x) = x * x
xs = [1, 2, 3, 4, 5]
squares[i] = square(xs[i])
// squares is [1, 4, 9, 16, 25]
fn normalize(x, min, max) = (x - min) / (max - min)
values = [10, 20, 30, 40, 50]
normalized[i] = normalize(values[i], 10, 50)
// normalized is [0, 0.25, 0.5, 0.75, 1]
No Arguments
Functions with no arguments still need parentheses:
fn pi() = 3.14159
fn newline() = "\n"
area = pi() * r * r
Return Behavior
In most cases, the last expression in a block function is its return value—no return keyword needed.
For early exit, use return(expr) with parentheses. return without parentheses is not valid:
fn divide(a, b) {
if (b == 0) { return(0) } // early exit
a / b // last expression is the return value
}
Inside a value-producing conditional, return() follows the conditional semantics described in Conditionals.
fn classify(x) {
category = if (x > 0) {
"positive"
} else {
"negative"
}
// Execution continues here
print("Classified as: " + category)
category
}
Early Return
Use return(expr) to exit a function early:
fn find_index(xs, target) {
idx, found := evolve { idx = -1, found = false, i = 0 } for (x in xs) {
if (x == target) {
idx' = i
found' = true
break
}
i' = i + 1
}
if (found) { return(idx) }
-1
}
Examples
Computing factorial:
fn factorial(n) {
result := evolve { result = 1, i = 1 } while (i <= n) {
result' = result * i
i' = i + 1
}
result
}
Checking if a value is in a list:
fn contains(xs, target) {
found = evolve (found = false) for (x in xs) {
if (x == target) {
found' = true
break
}
}
found
}
Computing the mean:
fn mean(xs) {
sum, count := evolve { sum = 0, count = 0 } for (x in xs) {
sum' = sum + x
count' = count + 1
}
sum / count
}
Vector operations:
import sqrt from math
fn dot_product(a, b) {
sum := evolve { sum = 0, i = 0 } for (x in a) {
sum' = sum + x * b[i]
i' = i + 1
}
sum
}
fn magnitude(v) {
sum_squares = evolve (sum_squares = 0) for (x in v) {
sum_squares' = sum_squares + x * x
}
sqrt(sum_squares)
}
String utilities:
fn repeat(s, n) {
result := evolve { result = "", i = 0 } while (i < n) {
result' = result + s
i' = i + 1
}
result
}
stars = repeat("*", 5) // "*****"
Filtering with a predicate:
fn count_where(xs, predicate) {
count = evolve (count = 0) for (x in xs) {
if (predicate(x)) {
count' = count + 1
}
}
count
}
fn is_positive(x) = x > 0
positives = count_where([-1, 2, -3, 4, 5], is_positive)
// positives is 3