Collections

Collections

std::collections is auto-imported, so these names are used bare.

Array

[public] class Array<T> impl Index<number, T>, IndexRef<number, T> {
    [public] fn size() => number
    [public] fn push(value: T)
    [public] fn is_empty() => bool
    [public] fn first() => T?
    [public] fn last() => T?
    [mutates] fn pop() => T?
    [mutates] fn set(i: number, value: T)
    [mutates] fn insert(i: number, value: T)
    [mutates] fn remove_at(i: number) => T
    [mutates] fn clear()
    [mutates] fn reverse()
    [public] fn iter() => ArrayIterator<T>
}

Functional operations, all taking closures:

[public] fn map<U>(transform: closure(T) => U) => Array<U>
[public] fn filter(predicate: closure(T) => bool) => Array<T>
[public] fn reduce<U>(initial: U, combine: closure(U, T) => U) => U
[public] fn find(predicate: closure(T) => bool) => T?
[public] fn any(predicate: closure(T) => bool) => bool
[public] fn all(predicate: closure(T) => bool) => bool
[public] fn for_each(action: closure(T) => void)
[public] fn zip<U>(other: Array<U>) => Array<Pair<T, U>>
[public] fn flat_map<U>(transform: closure(T) => Array<U>) => Array<U>
[public] fn take(amount: number) => Array<T>
[public] fn drop(amount: number) => Array<T>
[public] fn count_where(predicate: closure(T) => bool) => number

Sorting and searching are pluggable. The default algorithms are merge sort and linear search; set_sort_method and set_search_method change them, and sort_using / search_using override for one call.

items.sort(closure(a: string, b: string) => number {
    return a.size() - b.size()
})

A comparison closure returns a negative number when the first argument sorts first, zero when they tie, positive otherwise. A search probe returns negative when the element is below the target and zero on a match.

Two hazards. Indexing is not bounds checked, so an out-of-range index reads or writes past the buffer. And the search family returns -1 on no match, while find, first, last, and pop return optionals.

Literal shorthand

Arrays and maps have a literal form, so a small collection does not need a constructor and a run of push calls:

let names = #["alpha", "beta", "gamma"]
let ports = #{"http": 80, "https": 443}

An array literal is #[ followed by comma-separated values. A map literal is #{ followed by key: value pairs. Element and value types are inferred from the contents, so an annotation is optional:

let names: Array<string> = #["alpha", "beta"]

Both produce the ordinary Array and Map types, so everything on the rest of this page applies to them.

Map

[public] class Map<KT: impl Hash, impl Equals, VT> {
    [public] fn size() => number
    [public] fn set(key: KT, value: VT)
    [public] fn get(key: KT) => VT?
    [public] fn contains(key: KT) => bool
    [public] fn remove(key: KT)
    [public] fn key_list() => Array<KT>
    [public] fn value_list() => Array<VT>
    [public] fn entries() => Array<Pair<KT, VT>>
    [public] fn for_each(action: closure(KT, VT) => void)
}

Open addressing with linear probing, growing past three-quarters full. Key order is unspecified.

Map has no iter(), so for k in map is a compile error. Iterate map.key_list() or map.entries(), or use for_each.

IndexMap

Same surface as Map, but key_list, value_list, and entries return keys in insertion order. remove is linear rather than constant time. JsonObject is built on it, which is why parsed JSON round-trips with stable key order.

The rest

StringBuilder accumulates text and produces a string with build(). Pair<A, B> holds two values reached with get_first() and get_second(), and is what destructuring binds against. List<T> is Array<T> under a name that reads as a sequence. split(text, separator) returns an Array<string>; consecutive separators produce empty pieces, and an empty separator returns the whole text as one piece.