Collections

Building immutable collections

Mutable collections can start out empty and be built up. This doesn't work for immutable collections.
Below are the ways to initialize immutable collections, starting with the simplest:

  • Using new or list-new. See "new" functions.
  • Using for or for* loop. See Iteration.
  • Using build. This exposes a builder which is used to add elements one at a time.
    There is no way to inspect the elements in a builder until it's finished.
  • Using a mut-array, then calling move-to at the end. This is the most flexible approach.

To demonstrate each method:

main void() log (1, 2, 3)::nat[] log :: nat[] - 1 -- 2, 3 log for::nat[] x in 0 .. 3 x + 1 log for*::nat[] x in 0 .. 3 - x + 1 log with::nat[] out in build out ~= 1 out ~~= 2, 3 log out nat mut[] = () out ~= 1 out ~~= 2, 3 move-to::nat[] out

All of these methods can also be used to initialize mutable collections.

Arrays

The basic array operations are size and subscript.

main void() a nat[] = 1, 2, 3 log size a log a[0]

~~ concatenates two arrays. ~ prepends or appends an element to an array.

Array views

An array-view is a slice of an original array. It has similar functionality to an array, but with different performance.
Using an array-view avoids copying the array elements, but holds onto the entire original array even if only viewing a small part.
It also adds a level of indirection.
You should usually use arrays and only use a view when there is a clear advantage.

There are two ways to get an array-view:

Slicing can also index relative to the array size, such as a[1 .. end] which is equivalent to a[1 .. a.size] .

main void() a nat[] = 0, 1, 2, 3, 4 b nat array-view = a log b c nat array-view = a[1 .. 3] log c d nat array-view = a[1 .. end - 1] log d

To slice an array and get another array, use to.

main void() a nat[] = 0, 1, 2, 3, 4 log to::nat[] a[1 .. 3]

Collection conversion

Most collections define at least two to functions: one converting to an array and one converting from an array. This makes it possible to convert between any two collections by going through an array.

To convert to another collection while emptying the original collection, there is often a move-to function.

main void() a nat mut[] = 1, 2, 3 b = a.move-to::nat[] log a, b

Mutable arrays

A mutable array is written t mut[], equivalent to t mut-array .

main void() a nat mut[] = () a ~= 1 log a

To prevent bugs, a mut-array can't be mutated during iteration.

main void() a nat mut[] = 1, 2, 3 for x in a # This will fail a ~= x log a

Buffers

A buffer is like an array with mutable elements, but unlike a mut-array, it is fixed in size.

main void() a nat buffer = for x in 0 .. 10 then x a[5] := 55 log a

buffers aren't common, but are useful for low-level code. For example, mut-array is implemented using a buffer.
Low-level buffer use typically uses uninitialized-buffer, which is unsafe. (See Unsafe code.)

main void() a nat buffer = trusted uninitialized-buffer 10 a[5] := 55 # It's only safe to access elements that have been initialized log a[5]

A buffer-view works like an array-view.
Writing to a buffer-view affects the original buffer.

main void() a nat buffer = n-of 10, 0 view = a[5 .. 10] view[1] := 6 log a

Maps

A map is a collection of (key, value) pairs with unique keys that supports efficiently getting a value from the key.
The key must satisfy the key spec, which means ==, <=>, and hash must be defined for it.

A map type is written as value[key], equivalent to (key, value) map.

Map lookup uses the same subscript syntax as for arrays. Unlike for an array, it returns an option instead of throwing an exception on a missing key.

A map is constructed from a collection of tuples and iterates as tuples too.

main void() a string[nat] = - 1, "one" - 2, "two" for x in a log x log "a[1]", a[1] log "a[3]", a[3]

If the map key is symbol, it can be created with named-new syntax.

main void() a string[symbol] = one: "uno" two: "dos" log a["two"]

Map iteration

Maps are designed for efficient lookups. To accomplish this, they internally use hashing.
Hashing depends on a random seed. This makes it unsafe to expose to safe code (which should not behave randomly).

To keep it safe, map iteration sorts the map first.
If you will need to iterate a map frequently, consider using a sorted-map. This has the same functions as map but is always kept sorted, making iteration fast. The cost is that sorted-map is slower to look up keys than map.

If you need both fast iteration and the fastest possible lookups, you could call unsorted before iterating the map. This is unsafe. (That will be explained in Unsafe code.)

main void() a nat[symbol] = beta: 2, alpha: 1 for x in a log x log "unsorted:" trusted for x in unsorted a log x

Mutable maps

A mutable map is written as value mut[key], equivalent to (key, value) mut-map.

main void() a nat mut[symbol] = () a["alpha"] := 1 a["beta"] := 2 log a["alpha"] log remove a, "beta" log a["beta"]

Like with map, there is a mut-sorted-map that iterates faster at the expense of slower lookups.

Other collections

There are other collections in the standard library. Each has immutable and mutable versions.
There are also shared collections, which will be explained in Parallelism.

NameUse
counter, mut-counterTracks how many of each value it contains.
deque, mut-dequeFast to push or pop to/from either end.
priority-queue, mut-priority-queue(key, value) pairs sorted by key, with fast removal of the least key.
set, mut-setStores only unique values, with fast test for inclusion.
sorted-set, mut-sorted-setset with slower lookups but fast iteration.
stack, mut-stackFast to push or pop to/from the right.
queue, mut-queueFast to push to the right or pop from the left.