Set

The Set type has the following methods.

all

Set.all: (self: Set[T], predicate: T -> Bool) -> Bool

Return whether the predicate is true for all elements in the set. For example:

// Evaluates to true.
{11, 17, 42}.all(x => x > 0)

// Evaluates to false.
{11, 17, 42}.all(x => x > 20)

// Evaluates to true.
std.empty_set.all(x => false)

This method short-circuits: when the outcome is decided, it will not call the predicate for the remaining elements in the set.

any

Set.any: (self: Set[T], predicate: T -> Bool) -> Bool

Return whether the predicate is true for any element in the set. For example:

// Evaluates to true.
{11, 17, 42}.any(x => x > 17)

// Evaluates to false.
{11, 17, 42}.any(x => x > 42)

// Evaluates to false.
std.empty_set.any(x => true)

This method short-circuits: when the outcome is decided, it will not call the predicate for the remaining elements in the set.

contains

Set.contains: (self: Set[T], element: T) -> Bool

Return whether the set contains a given element. For example:

// Evaluates to [true, false].
[for needle in ["a", "z"]: {"a", "b", "c"}.contains(needle)]

except

Set.except: (self: Set[T], element: T) -> Set[T]

Return a copy of the set, with the given element omitted. If the element was not present in the first place, then the result is identical to the input.

{1, 2, 3}.except(2)
// Evaluates to:
{1, 3}

filter

Set.filter: (self: Set[T], predicate: T -> Bool) -> Set[T]

Construct a new set that contains only the elements where predicate returned true. The result is equivalent to a set comprehension, a and b are identical in this example:

let xs = {1, 2, 3};
let a = xs.filter(x => x > 1);
let b = {
  for x in xs:
  if x > 1:
  x
};
// Both a and b evaluate to:
{2, 3}

Set comprehensions are more general than filter: they support nested loops, and let-bindings are accessible to the inner scope. Still, filter can be useful, especially for iteratively refining a query in an rcl query command.

flat_map_dedup

Set.flat_map_dedup: (self: Set[T], map_element: T -> Set[U]) -> Set[U]

Construct a new set by taking every element in the set, applying map_element to it (which should return a collection), and concatenating those results. flat_map_dedup is like map_dedup, except that it flattens the result. It is equivalent to a set comprehension with a nested loop: a and b are identical in this example:

let apps = {
  { name = "sshd", ports = {22} },
  { name = "nginx", ports = {80, 443} },
  { name = "caddy", ports = {80, 443} },
};
let a = apps.flat_map_dedup(app => app.ports);
let b = {
  for app in apps:
  for port in app.ports:
  port
};
// Both a and b evaluate to:
{22, 80, 443}

Because flat_map_dedup returns a set, it implicitly removes duplicates from the result. To instead preserve all elements, use to_list with List.flat_map, or use a list comprehension. Note that the iteration order may not match the order in which the set is defined in code!

let a = apps.to_list().flat_map(app => app.ports);
let b = [for app in apps: ..app.ports];
// Both a and b evaluate to:
[80, 443, 80, 443, 22]

Set comprehensions are often clearer in configuration files, especially when the body is large. They are also more general: set comprehensions support arbitrary nesting, filtering with if, and let-bindings are accessible to the inner scope. Still, flat_map_dedup can be useful, especially for iteratively refining a query in an rcl query command.

group_by

Set.group_by: (self: Set[T], get_key: T -> U) -> Dict[U, Set[T]]

Group the elements of the set by a key selected by get_key.

let foods = {
  { category = "fruit", name = "apple" },
  { category = "fruit", name = "pear" },
  { category = "vegetable", name = "onion" },
  { category = "vegetable", name = "carrot" },
};
foods.group_by(food => food.category)

// Evaluates to:
{
  fruit = {
    { category = "fruit", name = "apple" },
    { category = "fruit", name = "pear" },
  },
  vegetable = {
    { category = "vegetable", name = "carrot" },
    { category = "vegetable", name = "onion" },
  },
}

key_by

Set.key_by: (self: Set[T], get_key: T -> U) -> Dict[U, T]

Build a dictionary with the key selected by get_key as key, and the set elements as values. The keys must be unique. When a key is not unique, this method fails and reports the conflicting values. See also List.key_by for an example.

map_dedup

Set.map_dedup: (self: Set[T], map_element: T -> U) -> Set[U]

Construct a new set by applying map_element to every element in the set. The result is equivalent to a set comprehension, a and b are identical in this example:

let fruits = {"orange", "apple", "banana"};
let a = {for fruit in fruits: fruit.len()};
let b = fruits.map_dedup(fruit => fruit.len());
// Both a and b evaluate to:
{5, 6}

Because map_dedup returns a set, it implicitly removes duplicates from the result. To instead preserve all elements, use to_list with List.map, or use a list comprehension. Note that the iteration order may not match the order in which the set is defined in code!

let a = [for fruit in fruits: fruit.len()];
let b = fruits.to_list().map(fruit => fruit.len());
// Both a and b evaluate to:
[5, 6, 6]

Set comprehensions are often clearer in configuration files, especially when the body is large. They are also more general: set comprehensions support nested loops and filtering with if. Still, map_dedup can be useful, especially for iteratively refining a query in an rcl query command.

len

Set.len: (self: Set[T]) -> Number

Return the number of elements in the set. For example:

// Evaluates to 3.
{1, 1, 2, 2, 3, 3}.len()

sort

Set.sort: (self: Set[T]) -> List[T]

Return a sorted version of the set. Elements of the same type will be sorted with respect to each other. The relative order of elements of different types is an implementation detail that may change between versions.

{11, 5, 7}.sort()
// Evaluates to:
[5, 7, 11]

sort_by

Set.sort_by: (self: Set[T], get_key: T -> U) -> List[T]

Return a copy of the set, sorted on a key selected by the function get_key. Keys are compared in the same way as for sort. The relative order is unspecified when keys compare equal, and may change in future versions.

let characters = {
  "Rachael",
  "Rick Deckard",
  "Gaff",
  "Pris",
  "Eldon Tyrell",
};
characters.sort_by(name => name.len())

// Evaluates to:
["Gaff", "Pris", "Rachael", "Eldon Tyrell", "Rick Deckard"]

sum

Set.sum: (self: Set[Number]) -> Number

Return the sum of the elements in the set. For example:

// Evaluates to 42.
{3, 7, 11, 21}.sum()

to_list

Set.to_list: (self: Set[T]) -> List[T]

Convert the set to a list. This is equivalent to a list comprehension:

let set = {1, 2, 3};
let a = [..set];
let b = set.to_list();
assert a == b: "Comprehension and to_list are equivalent";

Because sets are currently implemented as trees, the returned list is sorted, therefore this method is currently identical to sort. This may change in future versions. Such a change would be announced prominently in the changelog.

transitive_closure

Set.transitive_closure: (self: Set[T], expand: (T) -> Set[T]) -> Set[T]

Return the transitive closure of the relation expand. In graph terms, starting with a set of nodes self, the transitive closure is the set of all nodes that are reachable from the initial set of nodes, whether directly through an edge, or indirectly through multiple hops. The function expand takes one node, and should return the nodes one hop removed; it should follow the outgoing edges. It can return those nodes either as a Set[T] or List[T].

The transitive closure is often useful to flatten trees. For example:

let packages = {
  is-number = { version = "7.0.0" },
  is-odd = { version = "3.0.1", deps = ["is-number"] },
  is-even = { version = "1.0.0", deps = ["is-odd", "mocha"] },
  mocha = { version = "11.7.5", deps = ["diff", "picocolors"] },
  diff = { version = "8.0.2" },
  picocolors = { version = "1.1.1" },
};

// Evaluates to {"is-number", "is-odd"}.
{"is-odd"}.transitive_closure(p => packages[p].get("deps", []))

// Evaluates to {"diff", "is-even", "is-number", "is-odd", "mocha",
// "picocolors"}.
{"is-even"}.transitive_closure(p => packages[p].get("deps", []))

It is fine when multiple calls to expand return the same value, and even to return values that were already expanded, for example in a graph where nodes have multiple incoming edges or cycles:

let out_edges = { a = {"b", "c"}, b = {"c", "a"}, c = {"a", "b"} };

// Does not hang, despite the cycles in the graph.
// Evaluates to {"a", "b", "c"}.
{"a"}.transitive_closure(p => out_edges[p])

union

To take the union of sets, use unpack:

let xs = { 1, 2, 3 };
let ys = { 3, 4, 5 };
// Evaluates to { 1, 2, 3, 4, 5 }.
{ ..xs, ..ys }