Iterators and Iteration
Osprey has no for, while, or loop construct. Range pipelines provide the
iteration surface in both language flavors.
Core Iterator Functions — [BUILTIN-ITER]
Iterator<T> is the opaque type produced by range. It is distinct from a
materialized List<T> and is consumed by forEach or fold.
range(start: int, end: int) -> Iterator<int> — [BUILTIN-ITER-RANGE]
Generates integers from start (inclusive) to end (exclusive).
range(1, 5) // 1, 2, 3, 4
range (1, 5) // 1, 2, 3, 4
forEach(iterator: Iterator<T>, function: fn(T) -> Unit) -> Unit — [BUILTIN-ITER-FOREACH]
Applies function to each element for its side effects.
range(1, 5) |> forEach(print)
range (1, 5) |> forEach print
map(iterator: Iterator<T>, function: fn(T) -> U) -> Iterator<U> — [BUILTIN-ITER-MAP]
Transforms each element.
range(1, 5) |> map(double)
range (1, 5) |> map double
filter(iterator: Iterator<T>, predicate: fn(T) -> bool) -> Iterator<T> — [BUILTIN-ITER-FILTER]
Keeps elements that satisfy predicate.
range(1, 10) |> filter(isEven)
range (1, 10) |> filter isEven
fold(iterator: Iterator<T>, initial: U, function: fn(U, T) -> U) -> U — [BUILTIN-ITER-FOLD]
Reduces an iterator to a single value.
fn add(total: int, value: int) -> int = (total + value) ?: total
range(1, 5) |> fold(0, add) // 0+1+2+3+4 = 10
add : (int, int) -> int
add (total, value) = (total + value) ?: total
range (1, 5) |> fold (0, add) // 0+1+2+3+4 = 10
Callbacks and Accumulators — [BUILTIN-ITER-CALLBACK]
Callbacks may be lambdas, named functions, or function values. Generic named
functions are specialized at the call site. Iterator combinators preserve a
callback's complete return type, including Result<T, E>; they never unwrap a
failure channel. A callback used where a plain accumulator or record field is
required must handle checked integer arithmetic explicitly
(ARITH-CHECKED).
fn energy(p) = (p.mass + p.spin) ?: 0 // generic: inferred, no annotations
fn addEnergy(total: int, value: int) -> int = (total + value) ?: total
range(1, n) |> map(forge) |> map(energy) |> fold(0, addEnergy)
A fold accumulator may be any inferred type, including a record:
fn bump(p, step) = p { mass: (p.mass + 1) ?: p.mass }
range(1, n) |> fold(Particle { id: 0, mass: 0, spin: 0 }, bump) // -> Particle
Pipe Operator — [BUILTIN-ITER-PIPE]
|> passes its left operand as the first argument to the function on its right.
5 |> double |> print // print(double(5))
range(1, 10) |> forEach(print)
range(0, 20) |> filter(isEven) |> map(double) |> forEach(print)
5 |> double |> print // print(double(5))
range (1, 10) |> forEach print
range (0, 20) |> filter isEven |> map double |> forEach print
Stream Fusion — [BUILTIN-ITER-FUSION]
Chains of map and filter over a range are emitted as one loop when consumed
by forEach or fold; no intermediate collection is created:
range(1, 5) |> map(double) |> filter(isEven) |> forEach(print)
range (1, 5) |> map double |> filter isEven |> forEach print
is equivalent to:
for (i = 1; i < 5; i++) {
value = double(i);
if (isEven(value)) print(value);
}