Collections¶
Candela has two built-in collections, lists and maps, plus a set built on top of maps in the standard library. All three are passed by reference: handing one to a function lets that function change it.
Lists¶
A list is written in square brackets and holds elements of a single type.
fn main() {
let numbers = [1, 2, 3];
let words = ["alpha", "beta"];
let empty = [];
print(numbers, words, empty.len());
}
Mixing types in one list is a compile error: [1, "a"] does not compile.
The empty list¶
[] names no element type. A local that starts empty takes its element type
from the first push.
Where a declaration says what the elements are, that declaration decides them.
A parameter annotated T[] compiles the function body with elements of T
however the call site writes the list, and a -> T[] return annotation hands
the caller elements of T. So a function that reads its elements still works
when it is called with nothing in the list:
enum Cell { Num(int), Text(string) }
fn width(cells: Cell[]) -> int {
let w = 0;
for c in cells {
match c {
Cell::Num(n) => { w = w + 1; }
Cell::Text(t) => { w = w + t.len(); }
}
}
return w;
}
fn main() {
print(width([]), width([Cell::Text("ab"), Cell::Num(1)]));
}
A list that does name an element type is still checked against the annotation,
so width([1, 2]) does not compile. A let takes no annotation, so a local
that starts empty and is never pushed to keeps elements of no type. Handing such
a local to a parameter that declares its elements pins it as well, so what the
function pushes into it reads back at that type.
Indexing and slicing¶
Index from zero with xs[i]. A slice xs[start..end] returns a new list from
start up to but not including end, and xs[..end] starts at the beginning.
An index past the end raises at runtime.
fn main() {
let xs = [10, 20, 30, 40];
print(xs[0], xs[3]);
print(xs[1..3], xs[..2]);
xs[0] = 99;
print(xs);
}
Strings index and slice the same way, returning strings.
Growing and reordering¶
fn main() {
let xs = [3, 1, 2];
xs.push(4);
xs.sort();
print(xs);
xs.reverse();
print(xs);
xs.remove(0);
print(xs);
}
push, sort, reverse, and remove change the list in place. +
concatenates two lists into a new one.
Inspecting¶
fn main() {
let xs = [10, 20, 30];
print(xs.len(), xs.contains(20), xs.find(30));
print(["a", "b"].join(", "));
print([1, 0, 2, 0, 3].partition(0));
}
find returns the index of a value, or -1 when it is absent. join
concatenates a list of strings, with an optional separator. partition splits a
list on a separator element.
Iterating¶
Higher-order operations¶
Lists carry the standard library's list helpers as methods, with no import
needed.
fn double(x) {
return x * 2;
}
fn main() {
let xs = [1, 2, 3, 4];
print(xs.map(double));
print(xs.filter(fn(x) { return x % 2 == 0; }));
print(xs.reduce(0, fn(a, b) { return a + b; }));
print(xs.sum(), xs.min(), xs.max(), xs.first(), xs.last());
print(xs.take(2), xs.drop(2), xs.unique(), xs.chunk(2));
print(xs.any(fn(x) { return x > 3; }), xs.all(fn(x) { return x > 0; }));
}
The functions you pass read the variables around them, so a predicate can test against what the scope holds; see Functions.
Maps¶
A map is written in braces as key: value pairs. Keys share one type and values
share one type. {} is the empty map.
fn main() {
let ages = {"ada": 36, "alan": 41};
let by_number = {1: "one", 2: "two"};
let empty = {};
print(ages.len(), by_number.get(1), empty.len());
}
Repeating a key in a literal is a compile error.
The empty map¶
{} names neither a key type nor a value type, the same way [] names no
element type. Where a declaration says what the map holds, that declaration
decides it: a parameter annotated {K: V} compiles the function body with keys
of K and values of V however the call site writes the map, and a
-> {K: V} return annotation hands the caller the same. So a function that
matches on what a key holds still works when it is called with an empty map:
enum Cell { Num(int), Text(string) }
fn width(cells: {string: Cell}) -> int {
let w = 0;
for name in cells {
match cells.get(name) {
Cell::Num(n) => { w = w + 1; }
Cell::Text(t) => { w = w + t.len(); }
}
}
return w;
}
fn main() {
print(width({}), width({"a": Cell::Text("ab")}));
}
A map that does name its types is still checked against the annotation, so
width({"a": 1}) does not compile. A let takes no annotation, so a local that
starts empty and is never inserted into keeps keys and values of no type. Handing
such a local to a parameter that declares what the map holds pins it as well, so
what the function inserts reads back at that type.
Reading and writing¶
fn main() {
let scores = {"a": 1};
scores.insert("b", 2);
scores.insert("a", 10);
print(scores.get("a"), scores.len());
print(scores.contains("b"), scores.keys(), scores.values());
scores.remove("b");
print(scores.len());
}
insert adds a pair or replaces the value of an existing key. remove takes
the entry under a key back out, and a key the map does not hold leaves it as it
was. get raises when the key is absent, so test with contains first, or use
the get_or method from the standard library's map module to supply a
fallback.
Iterating¶
Iterating a map walks its keys in the order they went in, and a literal's keys
go in as written. keys, values, and printing read the same order. Inserting
a key that is already there replaces the value and leaves the entry where it
is; removing a key and inserting it again puts it at the end. Two maps holding
the same entries are equal whatever order they were built in.
fn main() {
let scores = {"a": 1, "b": 2};
let total = 0;
for key in scores {
total += scores.get(key);
}
print(total);
}
Sets¶
A set holds each value at most once. It comes from the set module as
Set<T>, a struct built out of a map. Name the member type when you make one.
import "std/set" as set;
fn main() {
let s = set::new<int>();
s.add(1);
s.add(2);
s.add(2);
print(s.len(), s.contains(2), s.members());
}
|, &, -, and ^^ are union, intersection, difference, and symmetric
difference; each combines two sets into a new one, and each has a named method
too.
import "std/set" as set;
fn main() {
let a = set::new<int>();
a.add(1);
a.add(2);
let b = set::new<int>();
b.add(2);
b.add(3);
print((a | b).members());
print(a.intersection(b).members());
}
A set is a struct, so it is not iterable itself; members gives you a list to
iterate, in the order the members were added. a == b compares the members and
ignores that order. The module is covered in full in
set.