Collections
Builders
Besides empty + mutation, you can construct a populated collection in one
expression. Element/key types are inferred from the first argument (keep the
elements homogeneous):
let xs = listOf(2, 3, 5, 7) // List<Integer>
let tags = setOf("a", "b", "a") // Set<String> (deduplicates)
let caps = mapOf("fr" to "Paris", "jp" to "Tokyo") // Map<String, String>
k to v pairs the key and value in mapOf. For an empty collection, use
empty List<T> / empty Set<T> / empty Map<K, V> (the type can't be inferred
with no elements).
List<T>
List<T> is a growable, mutable, typed list — a built-in generic, the same
way T[] arrays are. There's nothing to import; the compiler specializes it per
element type and the runtime stores elements contiguously, so it's a thin,
allocation-light layer (no boxing).
Creating one
Use empty (the zero/blank-value keyword) with a List<T> type:
let nums = empty List<Integer>
let names = empty List<String>
let items = empty List<Item> // element can be any type
Operations
nums.push(10) // append
nums.get(0) // element at index (bounds-checked; aborts if out of range)
nums.set(0, 99) // replace element
nums.len() // Integer count
nums.isEmpty() // Bool
nums.removeAt(0) // remove by index (shifts the rest down)
nums.clear() // empty it
Iterate with for:
let sum = 0
for n in nums { sum = sum + n }
A List<T> is a normal value: pass it to functions, store it in fields, return
it.
mapper totalQty(items: List<Item>) -> Integer {
let sum = 0
for it in items { sum = sum + it.qty }
return sum
}
Notes
- A
List<T>is a mutable handle (reference semantics): passing it to a function and mutating it is visible to the caller. - Element access is bounds-checked; an out-of-range
get/set/removeAtaborts. - See
examples/collections_demo.xi.
Functional operations on List<T>
Lists have a functional API driven by lambdas: { it ... } binds the element
implicitly, or { a, b => ... } names the parameters. Each call is inlined into a
loop, so chains are zero-overhead (no intermediate closures).
let nums = listOf(1, 2, 3, 4, 5)
nums.map { it * 2 } // List<U> — transform (U is the body's type)
nums.filter { it % 2 == 0 } // List<T> — keep matching
nums.filterNot { it > 3 } // List<T> — drop matching
nums.forEach { print(it) } // run a side effect per element
nums.fold(0) { acc, x => acc + x } // reduce with a seed
nums.reduce { a, b => a + b } // reduce, seed = first element
nums.sumOf { it } // sum of a projection (Integer/Number)
nums.count { it > 2 } // Integer — how many match
nums.any { it > 4 } // Bool
nums.all { it > 0 } // Bool
nums.none { it > 9 } // Bool
nums.joinToString(", ") { int_to_string(it) } // String, with a separator
nums.mapIndexed { i, x => i * 10 + x } // map with the index
nums.take(3) / nums.drop(2) // first n / skip n
nums.takeWhile { it < 4 } // prefix while the predicate holds
nums.dropWhile { it < 4 } // drop that prefix
nums.reversed() // reversed copy
nums.distinct() // unique elements, order preserved
nums.flatMap { listOf(it, it) } // map then flatten the result lists
nums.first() / nums.last() // ends (bounds-checked)
nums.toSet() // Set<T> of the elements
Lookups that can miss return an optional (T?), unwrapped with if let
(there's no null):
if let hit = nums.find { it > 4 } { use(hit) } // first match, or none
nums.firstOrNone() / nums.lastOrNone() // ends as optionals
nums.maxByOrNone { it.score } // element with the max key
nums.minByOrNone { it.score } // element with the min key
nums.average { it.score } // Number mean (0.0 if empty)
Sorting returns a sorted copy (numeric or String keys):
nums.sorted() / nums.sortedDescending() // natural order of the elements
people.sortedBy { it.age } // by a key projection
people.sortedByDescending { it.name }
Grouping and slicing into sublists:
people.groupBy { it.team } // Map<K, List<T>> — bucket by a key
people.associateBy { it.id } // Map<K, T> — index by a key
items.associateWith { it.price } // Map<T, V> — element -> value (T is primitive/String)
nums.chunked(3) // List<List<T>> — consecutive groups
nums.windowed(3) // List<List<T>> — sliding windows
groupBy results chain: people.groupBy { it.team }.get("x").average { it.age }.
Lazy sequences
asSequence() makes the pipeline lazy: the chain of lazy operators plus the
terminal compile into a single fused loop — no intermediate lists are built,
and take short-circuits.
let total = orders.asSequence()
.filter { it.active }
.map { it.total }
.fold(0.0) { a, b => a + b } // one loop, no intermediates
nums.asSequence().take(3).sum() // visits only the first 3 elements
- Lazy ops:
map,filter,filterNot,take(n),drop(n),takeWhile,dropWhile. - Terminals:
toList(),toSet(),forEach,fold(seed),sum(),count(),any,all,first(),firstOrNone().
They chain naturally — orders.filter { it.paid }.map { it.qty }.fold(0) { a, b => a + b }.
See examples/functional_demo.xi.
Pairs — Pair<A, B>
A two-value tuple. Build one with the to infix; read the members with .first
and .second. A and B can be any types (including lists or other pairs).
let p = "ada" to 36 // Pair<String, Integer>
p.first // "ada"
p.second // 36
Three List operations produce or consume pairs:
names.zip(ages) // List<Pair<String, Integer>> (truncated to the shorter)
nums.partition { it > 0 } // Pair<List<Integer> matching, List<Integer> not>
pairs.unzip // Pair<List<A>, List<B>> — splits a list of pairs back
let pairs = listOf("ada", "bo").zip(listOf(36, 28))
for p in pairs { print(p.first + " is " + int_to_string(p.second)) }
let parts = listOf(1, 2, 3, 4).partition { it % 2 == 0 }
parts.first.len() // 2 (evens)
parts.second.len() // 2 (odds)
let cols = pairs.unzip
cols.first // List<String> ["ada", "bo"]
cols.second // List<Integer> [36, 28]
See examples/pairs_demo.xi.
Set<T>
Set<T> is a hash set of unique elements — also a built-in generic, created
with empty. Element-type-erased like List, with String elements compared by
content (not by reference).
Creating one
let ids = empty Set<Integer>
let names = empty Set<String>
Operations
ids.add(1) // insert; idempotent (no-op if already present)
ids.contains(1) // Bool, O(1) average
ids.remove(1) // delete (no-op if absent)
ids.len() // Integer count of unique elements
ids.isEmpty() // Bool
ids.clear() // empty it
ids.items() // a List<T> snapshot of the live elements
Iterate with for (order is unspecified):
for x in ids { ... }
Notes
- Membership is by value: two
Strings with equal content are the same element; struct elements are compared by their bytes. - A
Set<T>is a mutable handle (reference semantics), likeList<T>. - Iteration order is not specified; use
items()if you need aListto sort.
Map<K, V>
Map<K, V> is a hash map — a built-in generic, created with empty. Keys are
primitives or String (compared by value, String by content); values can be
any type.
Creating one
let ages = empty Map<String, Integer>
let names = empty Map<Integer, String>
let byId = empty Map<String, Item> // value can be a compound
Operations
ages.put("ada", 37) // insert or overwrite
ages.get("ada") // value (aborts if the key is absent — guard with has)
ages.getOr("zz", 0) // value, or the fallback if absent
ages.has("ada") // Bool
ages.remove("ada") // delete (no-op if absent)
ages.len() // Integer entry count
ages.isEmpty() // Bool
ages.clear() // empty it
ages.keys() // a List<K> of the keys
ages.values() // a List<V> of the values
Since lookups can miss, iterate over the keys (no null in Ξ — a missing key
is simply not in keys()):
for k in ages.keys() {
let v = ages.get(k)
...
}
Notes
getaborts if the key is absent — usehasto check first, orgetOrfor a default. (AV?-returning lookup will follow once optionals are wired into the collection API.)- Keys are restricted to primitives and
String; values may be any type. - A
Map<K, V>is a mutable handle (reference semantics). Iteration order is unspecified.
Lazy infinite sources — generateSequence
generateSequence(seed) { next } is a lazy source whose value starts at seed
and advances through the generator each step. It fuses into the sequence loop like
any other source, so it's only as long as the bounded terminal asks for — always
bound it with take, takeWhile, or first:
generateSequence(1) { it * 2 }.take(8).toList() // [1,2,4,…,128]
generateSequence(1) { it + 1 }.take(10).fold(0) { a, b => a + b } // 55
generateSequence(0) { it + 2 }.takeWhile { it < 10 }.toList() // [0,2,4,6,8]
See examples/generate_sequence_demo.xi.
What's next
The collection layer is complete: containers (List/Set/Map), the eager
functional API, lazy sequences (incl. generateSequence), Pair<A, B>, and
zip/partition/unzip. The remaining language-level work — first-class
closures and generics — is tracked in the
closures & generics proposal.