Builtin Collections

Claro also rounds out its builtin types with a small set of convenient collection types that allow you to manipulate many values using a single variable. These are provided as builtins for your convenience, but their implementations have been hand selected to cover the majority of your general purpose programming use cases.

Ad-Hoc Declarations

Unlike many other languages (e.g. Java/C++/etc.) that require a formal declaration of any type before it can be instantiated, Claro's builtin collections can all be simply instantiated at will as if the type already exists. For example, any struct-like collection of named fields in Java would first require the declaration of a class, and potentially the declaration of other things like a constructor, hashCode() and equals() implementations. In Claro, you simply skip all the boilerplate.

For example, the following Claro procedure declares a struct {row: int, col: int} inline as the function's return type and doesn't need any top-level declaration of that type before it's used:

Fig 1:


function findInNestedList<T>(l: [[T]], t: T) -> struct {row: int, col: int} {
  var r = 0;
  for (row in l) {
    var c = 0;
    for (elem in row) {
      if (elem == t) {
        return {row = r, col = c};  # <-- Just instantiate the struct.
      }
      ++c;
    }
    ++r;
  }
  return {row = -1, col = -1};
}

[[1, 2],
 [3, 4],
 [5, 6]]
  |> findInNestedList(^, 4)
  |> print(^);

Output:

{row = 1, col = 1}

Mutability

All of Claro's builtin collection types come in either a mutable or immutable variant - by default, Claro will assume that any collection literals are intended to be immutable.

Fig 2:


var l = [1, 2, 3]; # Immutable list of ints.
print(l);

# The below line would be illegal as `lists::add` expects a mutable list.
# lists::add(l, 4);

Output:

[1, 2, 3]

The following example demonstrates initialization of a mutable list of integers:

Fig 3:


var l = mut [1, 2, 3];
print("Before: {l}");

lists::add(l, 4);      # <-- Mutation happens here.
print("After:  {l}");

Output:

Before: mut [1, 2, 3]
After:  mut [1, 2, 3, 4]

Mutability Annotations are Shallow

Claro's mutability annotations are shallow by design so that you maintain fine-grained control over creating arbitrarily complex nested data structures that mix mutability and immutability as needed. The following examples demonstrate different combinations of nested mutability annotations:

This example demonstrates a mutable list whose elements are immutable lists.

Fig 4:


var l: mut [[int]] = mut [];
for (i in [1, 2, 3]) {
  lists::add(l, [i, i]); # <-- Add an immutable list to the mutable list.
}
print(l);

Output:

[0.001s][warning][perf,memops] Cannot use file /tmp/hsperfdata_runner/6 because it is locked by another process (errno = 11)
mut [[1, 1], [2, 2], [3, 3]]

This example demonstrates an immutable list whose elements are mutable lists.

Fig 5:


var l: [mut [int]] = [mut [], mut [], mut []];
var i = 1;
for (mutList in l) {
  lists::add(mutList, i); # <-- Append an int to this inner mutable list.
  lists::add(mutList, i++);
}
print(l);

Output:

[mut [1, 1], mut [2, 2], mut [3, 3]]

Data Race Safety via Deep Immutability

This builtin support for mutability annotations allows Claro to enforce some very strong safety guarantees in concurrent contexts, so this is about more than just providing a convenient library of data types.

See the Concurrency section in this book for more details on how Claro will statically leverage knowledge of whether a type is deeply immutable or not to prevent unsafe data races.