Collections
Seventeen array builtins. The first five (range, sum, avg, sort, first/last) cover the common slicing-and-aggregating cases without writing a loop. push/pop are immutable helpers (the call returns a new array; the source is untouched). filter/map/reduce take lambdas — Rat's fn >> expr spelled inline at the call site. The rest is sugar — group_by, count_by, unique, flatten, chunk, zip — for the shape conversions that show up over and over in real apps.
range — generate a sequence
1, 2, or 3-arg forms
range(n) is [0, 1, ..., n-1]. range(start, end) is the half-open interval. range(start, end, step) walks with a step (the step can be negative for descending).
<p> [range(5)]
<p> [range(2, 6)]
<p> [range(0, 10, 3)] [0, 1, 2, 3, 4]
[2, 3, 4, 5]
[0, 3, 6, 9]
sum / avg — numeric aggregation
Empty array → 0
sum(arr) totals the values; avg(arr) returns the mean. Both expect numeric elements and return 0 on an empty array — guard upstream if "no data" must read differently from "data sums to zero".
<p> [sum(nums)]
<p> [avg(nums)] sum: 150
avg: 30
sort — stable ascending
Returns a new array; source unchanged
sort(arr) returns a new array sorted in ascending order using the default comparator (numbers numerically, strings lexicographically). It's stable, so equal keys keep their input order. For descending or custom orderings, sort then call reduce with the order you want, or use a small helper.
<p> [sort([3, 1, 4, 1, 5, 9])] [1, 1, 3, 4, 5, 9]
first / last — head and tail
One-arg form returns the element; two-arg returns N
first(arr) returns the first element (or null on empty); first(arr, n) returns the first n elements as an array. last is symmetric.
<p> [first(items)]
<p> [last(items)]
<p> [first(nums, 2)]
<p> [last(nums, 2)] first(items) = apples
last(items) = figs
first(nums, 2) = [10, 20]
last(nums, 2) = [40, 50]
push / pop — immutable wrappers
Returns a new array; for in-place use .add() / .pop() on state
push(arr, value) returns a new array with the value appended; pop(arr) returns the last element. Neither mutates the input — for in-place writes on page state, use name.add(v) / name.pop() in a handler instead.
<p> [push(items, 'kiwi')]
<p> [pop(items)] push: [apples, pears, figs, kiwi]
pop: figs
filter — keep matching elements
Lambda predicate; keeps truthy returns
filter(arr, lambda) returns the elements for which the lambda returns truthy. The lambda is one-arg; for index-aware filters write a custom reduce or use map first to attach indices.
<p> [filter(nums, [x] >> x > 20)] [30, 40, 50]
map — transform each element
Lambda returns the new element
map(arr, lambda) applies the lambda to each element and collects the results into a new array. The lambda can return any value — number, string, object — and the output array adopts whatever shape it returns.
<p> [map(nums, [x] >> x + 1)] [11, 21, 31, 41, 51]
reduce — fold to a single value
Lambda is (accumulator, element); third arg is the seed
reduce(arr, lambda, init) folds the array. The lambda takes the running accumulator and the current element; the third arg seeds the accumulator. Common idiom: build a histogram with reduce(arr, [acc, x] >> set_field(acc, x, get_or(acc, x, 0) + 1), {}) — though for the histogram case count_by is shorter.
<p> [reduce(nums, [a, b] >> a + b, 0)] 150
group_by — bucket by key
Returns an object: key → array of matching elements
group_by(arr, lambda) calls the lambda on each element and groups elements with equal lambda results into the same bucket. The result is an object — iterate with in keys(result) if you need the bucket name alongside the bucket.
<p> [group_by(nums, [x] >> x > 25)] {false: [10, 20], true: [30, 40, 50]}
count_by — histogram
Object: key → count
count_by(arr, lambda) is group_by + len on each bucket. Use it for cheap frequency counts — word length distributions, status code tallies, anything where you want "how many fell into each category".
<p> [count_by(items, [w] >> len(w))] {6: 1, 5: 1, 4: 1}
unique — deduplicate
Preserves first-seen order
unique(arr) removes duplicate elements, keeping the first occurrence of each. Useful for cleaning up tag lists, recipient sets, or any "list of distinct" output.
<p> [unique([1, 2, 2, 3, 1])] [1, 2, 3]
flatten — one level of nesting
arr of arrs → flat arr
flatten(arr) concatenates the inner arrays into one. It strips exactly one level — call it twice for two-deep, or write a recursive helper for arbitrary depth. The shallow form is deliberate; most real data is one-deep and a generic deep-flatten loses type structure.
<p> [flatten([[1, 2], [3], [4, 5]])] [1, 2, 3, 4, 5]
chunk — fixed-size sub-arrays
Last chunk may be shorter
chunk(arr, n) splits the array into sub-arrays of length n. The last chunk is shorter than n if the array doesn't divide evenly. Useful for paginating, rendering grids row by row, or batching API calls.
<p> [chunk(range(7), 3)] [[0, 1, 2], [3, 4, 5], [6]]
zip — tuple-merge two arrays
Result length = shorter input
zip(a, b) pairs up elements by index, producing an array of two-element arrays. If the inputs are different lengths, the longer one is truncated. Pair with map when you need a record shape: map(zip(keys, values), >> {k: pair0, v: pair1}).
<p> [zip([1, 2, 3], ['a', 'b', 'c'])] [[1, a], [2, b], [3, c]]