Higher-Order Functions
A higher-order function receives or returns another function. In Zolo,
the type fn(T) -> R describes a functional value. This allows abstracting
the how of an operation without repeating the what:
apply_twice, factory multiplier, compose, and the classic map/filter/reduce.
// Feature: Higher-order functions
// Syntax: `fn f(g: fn(T) -> R)` — function as argument or return value.
// When to use: abstract the "how" over the "what" — strategy
// pattern, callbacks, decorators, pipeline transformations.
use std::Array
// Function takes a function and applies it twice.
fn apply_twice(f: fn(int) -> int, x: int) -> int {
return f(f(x))
}
fn increment(x: int) -> int {
return x + 1
}
fn square(x: int) -> int {
return x * x
}
print(apply_twice(increment, 5)) // 7
print(apply_twice(square, 3)) // 81
// Function that RETURNS another function (factory).
fn multiplier(factor: int) {
return |x| x * factor
}
let triple = multiplier(3)
let quad = multiplier(4)
print(triple(7)) // 21
print(quad(7)) // 28
// Composition: `compose(f, g)(x) == f(g(x))`.
fn compose(f: fn(int) -> int, g: fn(int) -> int) {
return |x| f(g(x))
}
let inc_then_square = compose(square, increment)
print(inc_then_square(5)) // (5+1)^2 = 36
// Map/filter/reduce — bread-and-butter of higher-order.
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let doubled = nums.map(|x| x * 2)
let evens = nums.filter(|x| x % 2 == 0)
let sum = nums.reduce(|acc, x| acc + x, 0)
print(doubled) // [2, 4, ..., 20]
print(evens) // [2, 4, 6, 8, 10]
print(sum) // 55
When a closure captures a let mut from the outer scope, it can
accumulate state between calls. Each call to the factory function creates
an independent instance — the lightweight equivalent of an object with a single
method:
Counter, running-average accumulator, and the once pattern (runs only on the first call).
// Feature: Stateful closures — closures with mutable state
// Syntax: a closure captures `let mut` from the outer scope
// and modifies that value across calls.
// When to use: counters, lightweight generators, "memory"
// without creating a struct, factories of incrementers.
// Classic counter — each call increments.
fn make_counter() {
let mut count = 0
return |step| {
count = count + step
return count
}
}
let counter = make_counter()
print(counter(1)) // 1
print(counter(1)) // 2
print(counter(5)) // 7
// Counters are INDEPENDENT — each has its own `count`.
let c1 = make_counter()
let c2 = make_counter()
c1(10)
c1(10)
c2(1)
print(c1(0)) // 20 (accumulated 10+10)
print(c2(0)) // 1
// Accumulator with memory.
fn make_accumulator() {
let mut total = 0
let mut hits = 0
return |x| {
total = total + x
hits = hits + 1
return total / hits // running integer average
}
}
let avg = make_accumulator()
print(avg(10)) // 10
print(avg(20)) // 15
print(avg(30)) // 20
print(avg(40)) // 25
// "Once" — closure that only runs on the first call.
fn make_once(f: fn() -> int) {
let mut done = false
let mut result = 0
return || {
if !done {
result = f()
done = true
}
return result
}
}
let init = make_once(|| 42)
print(init()) // 42
print(init()) // 42 (cached)
Challenge
Use make_counter from the stateful-closures example to create two independent
counters, advance them at different rates, and verify that the values do not
mix.
See also