Library
Collections are values
Array, Map and Set never change in place; every changing method returns a new collection, and the idiom for change is to rebind.
since 0.1.0-alpha.1linuxwindowswasm
Description
Array<T>, Map<K, V> and Set<T> are pure values. A method that appears to change one of
them, such as add, with, without, insertAt or removeAt, builds and returns a new
collection and leaves the receiver untouched. To "change" a collection you rebind the variable
that holds it:
Array<int> xs = [1, 2];
xs = xs.add(3);
Map<string, int> ages = Map();
ages = ages.with("ann", 30);
Set<int> seen = Set();
seen = seen.with(7);
The bracket assignments xs[i] = v and m[k] = v are the same rebind written in shorter form:
they assign a new collection to the variable xs or m.
This has three consequences.
- A collection you pass to a function is safe. The function receives its own value. Anything it does to its parameter is visible only inside the function, and the caller's variable is unchanged. A function that wants to hand a changed collection back returns it.
- Copying is free to the program.
Array<int> b = a;givesbits own value. Changingblater never shows througha, and the other way round. The implementation shares storage and copies lazily (copy on write), but that is an optimisation you cannot observe; the language guarantees only the value behaviour. - A call that discards its result does nothing.
xs.add(3);as a statement compiles and has no effect, because the new array is dropped. Always assign the result.
The same rule for Array, Map and Set
void grow(Array<int> xs) {
xs[0] = 100;
xs = xs.add(1);
console.writeln(xs);
}
Array<int> a = [1, 2];
grow(a);
console.writeln(a);
Map<string, int> m = Map();
m = m.with("a", 1);
Map<string, int> copy = m;
copy["a"] = 50;
copy = copy.with("b", 2);
console.writeln(m);
console.writeln(copy);
Set<int> s = Set([1, 2]);
Set<int> t = s.with(3);
console.writeln(s.toString());
console.writeln(t.toString());
[100, 2, 1]
[1, 2]
{a: 1}
{a: 50, b: 2}
{1, 2}
{1, 2, 3}
Rules
- No method of
Array,MaporSetmutates its receiver. A method that changes contents returns a new collection. xs[i] = vandm[k] = vrebind the variable; they do not write into shared storage.- Passing a collection to a function, assigning it to another variable, or storing it in a field never lets a later change through one name show through another.
- The value rule covers the collection itself, not the objects inside it. A collection of class
instances holds references to those objects: a new collection made from it points at the same
instances, and a change made to an instance through one collection is seen through the other.
Struct elements are values:
a[i]gives a copy, so to change a struct element build a new struct and store it back witha[i] = P(...). MapandSetkeep insertion order. Replacing the value of an existing key in aMapkeeps that key in its original position; removing a key and adding it again puts it at the end.
Examples
Objects inside a collection stay shared
class Counter {
int n = 0;
void bump() { n = n + 1; }
}
Counter c = Counter();
Array<Counter> original = [c];
Array<Counter> longer = original.add(Counter());
longer[0].bump();
console.writeln(original[0].n);
console.writeln(longer.length());
console.writeln(original.length());
1
2
1
Notes
Collections stored in a class field follow the same rule: the field is a variable, and a method
that changes the collection assigns the new value back to the field, for example
items = items.add(x);.