Collections
Collection functions operate on arrays, strings, and objects. They are the workhorses of Tova's standard library -- you will use them in nearly every program.
All collection functions return new values rather than mutating the original data.
I/O
print
print(...args) -> NilOutputs to the console. Accepts multiple arguments, which are printed separated by spaces.
print("Hello, World!")
// Hello, World!
print("Name:", name, "Age:", age)
// Name: Alice Age: 30
print("Items: {len(items)}")
// Items: 5Length & Type
len
len(v) -> IntReturns the length of a string, array, or the number of keys in an object. Returns 0 for nil.
len([1, 2, 3]) // 3
len("hello") // 5
len({ a: 1, b: 2 }) // 2
len([]) // 0
len(nil) // 0type_of
type_of(v) -> StringReturns the Tova type name of a value. For custom type variants, returns the variant tag name.
type_of(42) // "Int"
type_of(3.14) // "Float"
type_of("hello") // "String"
type_of(true) // "Bool"
type_of([1, 2]) // "List"
type_of(nil) // "Nil"
type_of(print) // "Function"
type_of({ a: 1 }) // "Object"
// Custom type variants return their tag name
type_of(Ok(1)) // "Ok"
type_of(None) // "None"Generating & Transforming
range
range(end) -> List[Int]
range(start, end) -> List[Int]
range(start, end, step) -> List[Int]Generates an array of sequential integers. The end value is exclusive.
range(5) // [0, 1, 2, 3, 4]
range(2, 7) // [2, 3, 4, 5, 6]
range(0, 10, 2) // [0, 2, 4, 6, 8]
range(10, 0, -1) // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]// Common pattern: iterate n times
for i in range(5) {
print("Iteration {i}")
}filled
filled(n, value) -> List[T]Creates a pre-allocated array of n elements, all set to value. More efficient than building an array with a push loop.
filled(5, 0) // [0, 0, 0, 0, 0]
filled(3, "hello") // ["hello", "hello", "hello"]
filled(100, false) // [false, false, ..., false]// Use filled() instead of a push loop for constant values
var grid = filled(rows * cols, 0)enumerate
enumerate(arr) -> List[[Int, T]]Returns [index, value] pairs. Useful for iterating with an index.
enumerate(["a", "b", "c"])
// [[0, "a"], [1, "b"], [2, "c"]]
for i, item in enumerate(items) {
print("{i}: {item}")
}map
map(arr, fn) -> ListApplies a function to each element and returns a new array of results.
map([1, 2, 3], fn(x) x * 2)
// [2, 4, 6]
names = ["alice", "bob"]
map(names, capitalize)
// ["Alice", "Bob"]filter
filter(arr, fn) -> ListReturns a new array containing only elements where the function returns true.
filter([1, 2, 3, 4, 5], fn(x) x > 3)
// [4, 5]
evens = filter(range(10), fn(x) x % 2 == 0)
// [0, 2, 4, 6, 8]flat_map
flat_map(arr, fn) -> ListApplies a function to each element (which should return an array), then flattens the result one level.
flat_map([1, 2, 3], fn(x) [x, x * 10])
// [1, 10, 2, 20, 3, 30]
flat_map(["hello world", "foo bar"], fn(s) split(s, " "))
// ["hello", "world", "foo", "bar"]flatten
flatten(arr) -> ListFlattens a nested array by one level.
flatten([[1, 2], [3, 4], [5]])
// [1, 2, 3, 4, 5]
flatten([[1, [2]], [3]])
// [1, [2], 3] -- only one levelunique
unique(arr) -> ListReturns a new array with duplicate elements removed. Uses Set internally for deduplication.
unique([1, 2, 2, 3, 3, 3])
// [1, 2, 3]
unique(["a", "b", "a", "c"])
// ["a", "b", "c"]chunk
chunk(arr, n) -> List[List]Splits an array into chunks of size n. The last chunk may be smaller.
chunk([1, 2, 3, 4, 5], 2)
// [[1, 2], [3, 4], [5]]
chunk(range(9), 3)
// [[0, 1, 2], [3, 4, 5], [6, 7, 8]]Reducing & Aggregating
reduce
reduce(arr, fn, init?) -> TFolds an array into a single value using an accumulator function. If init is omitted, the first element is used as the initial value.
reduce([1, 2, 3, 4], fn(acc, x) acc + x, 0)
// 10
reduce(["a", "b", "c"], fn(acc, x) acc + x, "")
// "abc"
// Without initial value
reduce([1, 2, 3], fn(acc, x) acc * x)
// 6sum
sum(arr) -> NumberReturns the sum of all elements in the array. Equivalent to reduce(arr, fn(a, b) a + b, 0).
sum([1, 2, 3, 4]) // 10
sum([]) // 0
sum(range(101)) // 5050count
count(arr, fn) -> IntCounts the number of elements that satisfy a predicate.
count([1, 2, 3, 4, 5], fn(x) x > 3)
// 2
count(["apple", "avocado", "banana"], fn(s) starts_with(s, "a"))
// 2min
min(arr) -> T | NilReturns the minimum element in the array. Returns nil for an empty array.
min([3, 1, 4, 1, 5]) // 1
min(["c", "a", "b"]) // "a"
min([]) // nilmax
max(arr) -> T | NilReturns the maximum element in the array. Returns nil for an empty array.
max([3, 1, 4, 1, 5]) // 5
max(["c", "a", "b"]) // "c"
max([]) // nilSearching
find
find(arr, fn) -> T | NilReturns the first element where the function returns true. Returns nil if no element matches.
find([1, 2, 3, 4], fn(x) x > 2)
// 3
find(users, fn(u) u.name == "Alice")
// { name: "Alice", age: 30 }
find([1, 2, 3], fn(x) x > 10)
// nilfind_index
find_index(arr, fn) -> Int | NilReturns the index of the first element where the function returns true. Returns nil if no element matches.
find_index([10, 20, 30], fn(x) x > 15)
// 1
find_index(["a", "b", "c"], fn(s) s == "b")
// 1
find_index([1, 2, 3], fn(x) x > 10)
// nilincludes
includes(arr, value) -> BoolReturns true if the array contains the given value.
includes([1, 2, 3], 2) // true
includes([1, 2, 3], 5) // false
includes(["a", "b"], "a") // trueany
any(arr, fn) -> BoolReturns true if any element satisfies the predicate.
any([1, 2, 3], fn(x) x > 2) // true
any([1, 2, 3], fn(x) x > 10) // false
any([], fn(x) true) // falseall
all(arr, fn) -> BoolReturns true if all elements satisfy the predicate. Returns true for an empty array.
all([2, 4, 6], fn(x) x % 2 == 0) // true
all([2, 3, 6], fn(x) x % 2 == 0) // false
all([], fn(x) false) // trueOrdering & Slicing
sorted
sorted(arr, keyFn?) -> ListReturns a sorted copy of the array. An optional key function specifies what to sort by.
sorted([3, 1, 4, 1, 5])
// [1, 1, 3, 4, 5]
sorted(["banana", "apple", "cherry"])
// ["apple", "banana", "cherry"]
// Sort by key function
sorted(users, fn(u) u.age)
// sorts users by age ascending
sorted(items, fn(x) -x.price)
// sorts by price descendingreversed
reversed(arr) -> ListReturns a reversed copy of the array.
reversed([1, 2, 3]) // [3, 2, 1]
reversed("hello" |> chars()) // ["o", "l", "l", "e", "h"]take
take(arr, n) -> ListReturns the first n elements.
take([1, 2, 3, 4, 5], 3) // [1, 2, 3]
take([1, 2], 10) // [1, 2]drop
drop(arr, n) -> ListReturns the array with the first n elements removed.
drop([1, 2, 3, 4, 5], 2) // [3, 4, 5]
drop([1, 2], 10) // []first
first(arr) -> T | NilReturns the first element, or nil if the array is empty.
first([10, 20, 30]) // 10
first([]) // nillast
last(arr) -> T | NilReturns the last element, or nil if the array is empty.
last([10, 20, 30]) // 30
last([]) // nilCombining & Splitting
zip
zip(...arrays) -> List[List]Combines multiple arrays into an array of tuples. Truncates to the length of the shortest array.
zip([1, 2, 3], ["a", "b", "c"])
// [[1, "a"], [2, "b"], [3, "c"]]
zip([1, 2], ["a", "b"], [true, false])
// [[1, "a", true], [2, "b", false]]
// Truncates to shortest
zip([1, 2, 3], ["a", "b"])
// [[1, "a"], [2, "b"]]partition
partition(arr, fn) -> [List, List]Splits an array into two arrays: elements that pass the predicate and elements that fail it.
partition([1, 2, 3, 4, 5], fn(x) x % 2 == 0)
// [[2, 4], [1, 3, 5]]
evens, odds = partition(range(10), fn(x) x % 2 == 0)group_by
group_by(arr, fn) -> ObjectGroups elements into an object by the key returned from the function.
group_by(["apple", "avocado", "banana", "blueberry"], fn(s) chars(s) |> first())
// { a: ["apple", "avocado"], b: ["banana", "blueberry"] }
group_by(users, fn(u) u.role)
// { admin: [...], user: [...] }Advanced Collections
zip_with
zip_with(a, b, fn) -> ListCombines two arrays element-by-element using a function. Like zip followed by map, but in one step.
zip_with([1, 2, 3], [10, 20, 30], fn(a, b) a + b)
// [11, 22, 33]
zip_with(["Alice", "Bob"], [30, 25], fn(name, age) { name: name, age: age })
// [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }]frequencies
frequencies(arr) -> ObjectCounts how often each value appears. Returns an object of value-to-count pairs.
frequencies(["a", "b", "a", "c", "b", "a"])
// { a: 3, b: 2, c: 1 }
frequencies([1, 2, 2, 3, 3, 3])
// { "1": 1, "2": 2, "3": 3 }scan
scan(arr, fn, init) -> ListLike reduce, but returns all intermediate results. Useful for running totals.
scan([1, 2, 3, 4], fn(acc, x) acc + x, 0)
// [1, 3, 6, 10]
scan([100, -20, 50, -10], fn(bal, tx) bal + tx, 0)
// [100, 80, 130, 120]min_by
min_by(arr, fn) -> T | NilReturns the element with the smallest key as determined by the function. Returns nil for empty arrays.
min_by([{n: 3}, {n: 1}, {n: 2}], fn(x) x.n)
// { n: 1 }
min_by(["hello", "hi", "hey"], fn(s) len(s))
// "hi"
min_by([], fn(x) x)
// nilmax_by
max_by(arr, fn) -> T | NilReturns the element with the largest key as determined by the function. Returns nil for empty arrays.
max_by([{n: 3}, {n: 1}, {n: 2}], fn(x) x.n)
// { n: 3 }
max_by(users, fn(u) u.age)
// user with highest agesum_by
sum_by(arr, fn) -> NumberSums the results of applying a function to each element. Shorthand for map + sum.
sum_by([{v: 10}, {v: 20}, {v: 30}], fn(x) x.v)
// 60
sum_by(cart, fn(item) item.price * item.qty)
// total costproduct
product(arr) -> NumberMultiplies all elements in the array. Returns 1 for an empty array.
product([1, 2, 3, 4]) // 24
product([5]) // 5
product([2, 0, 10]) // 0sliding_window
sliding_window(arr, n) -> List[List]Returns all contiguous sub-arrays of size n. Useful for moving averages, pattern detection.
sliding_window([1, 2, 3, 4], 2)
// [[1, 2], [2, 3], [3, 4]]
sliding_window([1, 2, 3, 4, 5], 3)
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
sliding_window([1, 2], 5)
// [] -- window larger than arraySet Operations
union
union(a, b) -> ListReturns a new array containing all unique elements from both arrays. Also works on Tables for table union.
union([1, 2, 3], [3, 4, 5]) // [1, 2, 3, 4, 5]
union(["a", "b"], ["b", "c"]) // ["a", "b", "c"]intersection
intersection(a, b) -> ListReturns elements present in both arrays.
intersection([1, 2, 3], [2, 3, 4]) // [2, 3]
intersection([1, 2], [3, 4]) // []difference
difference(a, b) -> ListReturns elements in a that are not in b.
difference([1, 2, 3], [2, 3, 4]) // [1]
difference([1, 2, 3], [4, 5]) // [1, 2, 3]symmetric_difference
symmetric_difference(a, b) -> ListReturns elements in either array but not both.
symmetric_difference([1, 2, 3], [2, 3, 4]) // [1, 4]
symmetric_difference([1, 2], [1, 2]) // []is_subset
is_subset(a, b) -> BoolReturns true if every element of a is in b.
is_subset([1, 2], [1, 2, 3]) // true
is_subset([1, 4], [1, 2, 3]) // false
is_subset([], [1, 2]) // trueis_superset
is_superset(a, b) -> BoolReturns true if a contains every element of b.
is_superset([1, 2, 3], [1, 2]) // true
is_superset([1, 2], [1, 2, 3]) // falseItertools
pairwise
pairwise(arr) -> List[[T, T]]Returns adjacent pairs from the array.
pairwise([1, 2, 3, 4]) // [[1, 2], [2, 3], [3, 4]]
pairwise([1]) // []
pairwise([]) // []combinations
combinations(arr, r) -> List[List]Returns all r-length combinations of elements (order does not matter).
combinations([1, 2, 3, 4], 2)
// [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
combinations(["a", "b", "c"], 2)
// [["a", "b"], ["a", "c"], ["b", "c"]]permutations
permutations(arr, r?) -> List[List]Returns all r-length permutations of elements (order matters). If r is omitted, returns full-length permutations.
permutations([1, 2, 3], 2)
// [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
permutations([1, 2, 3])
// all 6 orderings of [1, 2, 3]intersperse
intersperse(arr, sep) -> ListInserts sep between every element.
intersperse([1, 2, 3], 0) // [1, 0, 2, 0, 3]
intersperse(["a", "b"], "-") // ["a", "-", "b"]
intersperse([1], 0) // [1]interleave
interleave(...arrays) -> ListInterleaves elements from multiple arrays.
interleave([1, 2], ["a", "b"]) // [1, "a", 2, "b"]
interleave([1, 2, 3], ["a", "b"]) // [1, "a", 2, "b", 3]repeat_value
repeat_value(val, n) -> ListCreates an array of n copies of val.
repeat_value(0, 5) // [0, 0, 0, 0, 0]
repeat_value("x", 3) // ["x", "x", "x"]Array Utilities
binary_search
binary_search(arr, target, keyFn?) -> IntPerforms binary search on a sorted array. Returns the index of the target, or -1 if not found.
binary_search([1, 3, 5, 7, 9], 5) // 2
binary_search([1, 3, 5, 7, 9], 4) // -1
// With key function
items = [{ id: 1 }, { id: 3 }, { id: 5 }]
binary_search(items, 3, fn(x) x.id) // 1is_sorted
is_sorted(arr, keyFn?) -> BoolReturns true if the array is sorted in ascending order.
is_sorted([1, 2, 3, 4]) // true
is_sorted([1, 3, 2]) // false
is_sorted([]) // true
// With key function
is_sorted([{n: 1}, {n: 2}, {n: 3}], fn(x) x.n) // truecompact
compact(arr) -> ListRemoves nil values from an array. Keeps other falsy values like 0, "", and false.
compact([1, nil, 2, nil, 3]) // [1, 2, 3]
compact([0, "", false, nil]) // [0, "", false]rotate
rotate(arr, n) -> ListRotates an array by n positions. Positive n rotates left, negative rotates right.
rotate([1, 2, 3, 4, 5], 2) // [3, 4, 5, 1, 2]
rotate([1, 2, 3, 4, 5], -1) // [5, 1, 2, 3, 4]insert_at
insert_at(arr, idx, val) -> ListReturns a new array with val inserted at position idx. Does not mutate the original.
insert_at([1, 2, 3], 1, "x") // [1, "x", 2, 3]
insert_at([1, 2], 0, 0) // [0, 1, 2]remove_at
remove_at(arr, idx) -> ListReturns a new array with the element at idx removed. Does not mutate the original.
remove_at([1, 2, 3], 1) // [1, 3]
remove_at(["a", "b", "c"], 0) // ["b", "c"]update_at
update_at(arr, idx, val) -> ListReturns a new array with the element at idx replaced by val. Does not mutate the original.
update_at([1, 2, 3], 1, "x") // [1, "x", 3]
update_at(["a", "b", "c"], 2, "z") // ["a", "b", "z"]Ordering
Ordering Type
The Ordering type represents the result of comparing two values:
| Variant | Description |
|---|---|
Less | First value is smaller |
Equal | Values are equal |
Greater | First value is larger |
compare
compare(a, b) -> OrderingCompares two values and returns their ordering.
compare(1, 2) // Less
compare(5, 5) // Equal
compare("b", "a") // GreaterUseful for custom sort functions:
items |> sorted(fn(a, b) {
match compare(a.priority, b.priority) {
Equal => compare(a.name, b.name)
result => result
}
})compare_by
compare_by(a, b, fn) -> OrderingCompares two values by applying a key function first, then comparing the results.
compare_by(user_a, user_b, fn(u) u.age) // compare by age
compare_by("hello", "hi", fn(s) len(s)) // compare by lengthPipeline Examples
These functions compose beautifully with the pipe operator |>:
// Find the top 3 most expensive items
items
|> sorted(fn(x) -x.price)
|> take(3)
|> map(fn(x) x.name)
// Word frequency count
text
|> lower()
|> words()
|> group_by(fn(w) w)
|> entries()
|> map(fn(pair) { word: pair[0], count: len(pair[1]) })
|> sorted(fn(x) -x.count)
// Flatten, deduplicate, and sort
nested_lists
|> flatten()
|> unique()
|> sorted()
// Set operations in pipelines
users_a
|> intersection(users_b)
|> sorted(fn(u) u.name)
// Immutable array updates
todos
|> insert_at(0, new_todo)
|> remove_at(completed_idx)Parallel Processing
parallel_map
await parallel_map(arr, f) -> [T]
await parallel_map(arr, f, num_workers) -> [T]Distributes array processing across multiple CPU cores using a persistent worker pool. Workers are created once and reused across calls.
results = await parallel_map(large_dataset, fn(item) {
expensive_computation(item)
})
// Specify number of workers
results = await parallel_map(data, process_item, 8)- Automatically detects available CPU cores
- Falls back to sequential processing for arrays smaller than 4 elements
- Workers persist across calls (no startup overhead on subsequent calls)
- Returns results in the same order as the input array
Use parallel_map for CPU-bound work on large arrays. For I/O-bound work (network requests, file reads), use async with parallel() instead.
See the Performance guide for benchmarks and usage patterns.