Collection types
Rell supports the following collection types:
list<T>- An ordered list that allows duplicate elements.set<T>- An unordered set that does not contain duplicate elements.map<K,V>- A key-value map.
Properties of collection types
- Mutable: You can dynamically add or remove elements.
- Map key or set element: Only non-mutable types can be used as map keys or set elements.
Mutable collection types
- Collection types (list, set, map): Always mutable.
- Nullable type: Only if the underlying type is mutable.
- Struct type: If the struct has a mutable field or a field of a mutable type.
- Tuple: If an element type is mutable.
Creating collections
// list
val l1 = [1, 2, 3, 4, 5, 1];
val l2 = list < integer > ();
// set
val s = set < integer > ();
// map
val m1 = ['Bob': 123, 'Alice': 456];
val m2 = map < text, integer > ();
print(l1); // [1, 2, 3, 4, 5, 1]
print(l2); // []
print(s); // []
print(m1); // {Bob=123, Alice=456}
print(m2); // {}
Combining collections
Since 0.14.16, the +, -, and & operators combine lists, sets, and maps. The operands are copied (shallow copies,
not deep), so for collections of immutable types these operators preserve immutability. Each operator has an equivalent
copying function, named to match the existing mutating functions (add_all_copy() is the copying counterpart of
add_all()).
Where you would previously write:
val c = list(a);
c.add_all(b);
you can now write:
val c = a + b;
// or
val c = a.add_all_copy(b);
| Operator | Types | Function | Result |
|---|---|---|---|
a + b | list | a.add_all_copy(b) | Concatenation of the two lists. |
a - b | list | a.remove_all_copy(b) | The elements of a without any element that occurs in b. |
a & b | list | a.retain_all_copy(b) | The elements found in both a and b. |
a + b | set | a.add_all_copy(b) | Union of the two sets. |
a - b | set | a.remove_all_copy(b) | The elements of a without any element that occurs in b. |
a & b | set | a.retain_all_copy(b) | Intersection of the two sets. |
a + b | map | a.put_all_copy(b) | Merge of the two maps. For keys present in both, the value of b wins. |
[1, 2, 3] + [2, 3, 4]; // [1, 2, 3, 2, 3, 4]
[1, 2, 3] - [2, 3, 4]; // [1]
[1, 2, 3] & [2, 3, 4]; // [2, 3]
set([1, 2, 3]) + set([2, 3, 4]); // set([1, 2, 3, 4])
set([1, 2, 3]) - set([2, 3, 4]); // set([1])
set([1, 2, 3]) & set([2, 3, 4]); // set([2, 3])
[1: 'a', 2: 'b'] + [2: 'Z', 3: 'c']; // [1: 'a', 2: 'Z', 3: 'c']
The mutating function
collection<T>.retain_all(values)
was added at the same time. It removes from the collection any element not contained in the given collection, and
returns true if anything was removed.
list<T>
A list is an ordered collection type that allows duplicate elements.
Constructors
-
list<T>()- Creates a new empty list. -
list<T>(list<T>)- Creates a copy of the given list (a list of a subtype is also acceptable). -
list<T>(set<T>)- Creates a copy of the given set (a set of a subtype is also acceptable).
Functions
| Function | Description |
|---|---|
.add(T): boolean | Adds an element to the end of the list. Always returns true. |
.add(pos: integer, T): boolean | Inserts an element at a given position in the list. Always returns true. |
.add_all(list<T>): boolean | Adds all elements from the given list to the end of the list. |
.add_all(set<T>): boolean | Adds all elements from the given set to the end of the list. |
.add_all(pos: integer, list<T>): boolean | Inserts all elements from the given list at the specified position in the list. |
.add_all(pos: integer, set<T>): boolean | Inserts all elements from the given set at the specified position in the list. |
.clear() | Removes all elements from the list. |
.contains(T): boolean | Checks if the list contains the specified element. |
.contains_all(list<T>): boolean | Checks if the list contains all elements from the given list. |
.contains_all(set<T>): boolean | Checks if the list contains all elements from the given set. |
.empty(): boolean | Returns true if the list is empty, otherwise returns false. |
.index_of(T): integer | Returns the position of the first occurrence of the element in the list. Returns -1 if the element isn't found. |
.remove(T): boolean | Removes the first occurrence of the specified element from the list. Returns true if found. |
.remove_all(list<T>): boolean | Removes all elements from the list that are present in the given list. |
.remove_all(set<T>): boolean | Removes all elements from the list that are present in the given set. |
.remove_at(pos: integer): T | Removes the element at the given position in the list and returns it. |
.repeat(n: integer): list<T> | Returns a new list, which is the old list repeated "n" times. Always creates a new list (even if n = 1). |
.size(): integer | Returns the number of elements in the list. |
.reverse() | Reverses the order of elements in the list in place. Returns nothing. |
.reversed(): list<T> | Returns a reversed copy of the list. Always creates a new list (even if the old list is empty or has one element). |
._sort() | Sorts the list in ascending order. Returns nothing. |
.sorted(): list<T> | Returns a sorted copy of the list in ascending order. |
.to_text(): text | Returns a text representation of the list, e.g., '[1, 2, 3, 4, 5]'. |
.sub(start: integer[, end: integer]): list<T> | Returns a sub-list (start-inclusive, end-exclusive) from the list. |
Special operators
[]- Accesses or modifies an element at a specified index in the list.in- Checks if a specified value exists in the list, returningtrueif it does.
Example:
struct Transaction {
id: integer;
amount: integer;
sender: text;
recipient: text;
}
function example_list() {
// Creating a list of transactions
val transactions = list<Transaction>();
// Adding transactions to the list
transactions.add(Transaction(id = 1, amount = 100, sender = "Alice", recipient = "Bob"));
transactions.add(Transaction(id = 2, amount = 200, sender = "Bob", recipient = "Charlie"));
transactions.add(Transaction(id = 3, amount = 150, sender = "Charlie", recipient = "Alice"));
// Accessing a transaction by index
val firstTransaction = transactions[0];
print("First transaction amount: %d".format(firstTransaction.amount));
// Modifying a transaction at a specific index
transactions[1] = Transaction(id = 2, amount = 250, sender = "Bob", recipient = "Charlie");
// Using contains with a custom equality check
val searchTransaction = Transaction(id = 3, amount = 150, sender = "Charlie", recipient = "Alice");
if (searchTransaction in transactions) {
print("Transaction found in the list");
}
// Using sub to get a portion of the list
val subList = transactions.sub(0, 2);
print("Sub-list size: %s".format(subList.size()));
// Creating a reversed copy of the list
val reversedList = transactions.reversed();
// Converting the list to text
print("Transactions: %s".format(transactions.to_text()));
}
example_list();
set<T>
An unordered collection type that does not allow duplicate elements.
struct Address {
value: text;
}
function example_set() {
// Creating a set of unique addresses
val uniqueAddresses = set<Address>();
// Adding addresses to the set
uniqueAddresses.add(Address("0xabc123"));
uniqueAddresses.add(Address("0xdef456"));
uniqueAddresses.add(Address("0xabc123")); // This won't be added as it's a duplicate
// Checking if an address exists in the set
if (Address("0xabc123") in uniqueAddresses) {
print("Address 0xabc123 is in the set");
}
// Removing an address from the set
uniqueAddresses.remove(Address("0xabc123"));
// Adding multiple addresses at once
val newAddresses = [Address("0xghi789"), Address("0xjkl012")];
uniqueAddresses.add_all(newAddresses);
// Printing the set
print("Unique addresses: %s".format(uniqueAddresses.to_text()));
}
example_set();
Constructors
-
set<T>()- Creates a new empty set. -
set<T>(set<T>)- Creates a copy of the given set (a set of a subtype is also acceptable). -
set<T>(list<T>)- Creates a copy of the given list (with duplicates removed).
Functions
| Function | Description |
|---|---|
.add(T): boolean | If the element isn't in the set, adds it and returns true. |
.add_all(list<T>): boolean | Adds all elements from the given list to the set. Returns true if at least one element was added. |
.add_all(set<T>): boolean | Adds all elements from the given set to the set. Returns true if at least one element was added. |
.clear() | Removes all elements from the set. |
.contains(T): boolean | Checks if the set contains the specified element. |
.contains_all(list<T>): boolean | Checks if the set contains all elements from the given list. |
.contains_all(set<T>): boolean | Checks if the set contains all elements from the given set. |
.empty(): boolean | Returns true if the set is empty, otherwise returns false. |
.remove(T): boolean | Removes the specified element from the set. Returns true if the element was found and removed. |
.remove_all(list<T>): boolean | Removes all elements from the set that are present in the given list. Returns true if at least one element was removed. |
.remove_all(set<T>): boolean | Removes all elements from the set that are present in the given set. Returns true if at least one element was removed. |
.size(): integer | Returns the number of elements in the set. |
.sorted(): list<T> | Returns a sorted copy of the set as a list. |
.to_text(): text | Returns a text representation of the set, e.g., '[1, 2, 3, 4, 5]'. |
Special operators
in- Checks if a specified value exists in the set, returningtrueif it does.
Example:
struct Address {
value: text;
}
function example_set() {
// Creating a set of unique addresses
val uniqueAddresses = set<Address>();
// Adding addresses to the set
uniqueAddresses.add(Address("0xabc123"));
uniqueAddresses.add(Address("0xdef456"));
uniqueAddresses.add(Address("0xabc123")); // This won't be added as it's a duplicate
// Using 'in' operator to check if an address exists in the set
if (Address("0xabc123") in uniqueAddresses) { //'in' operator
print("Address 0xabc123 is in the set");
}
// Additional examples using the 'in' operator
val addressesToCheck = [
Address("0xabc123"),
Address("0xdef456"),
Address("0xghi789"),
Address("0xjkl012")
];
for (addr in addressesToCheck) {
if (addr in uniqueAddresses) { //'in' operator
print("Address %s is in the set".format(addr.value));
} else {
print("Address %s is not in the set".format(addr.value));
}
}
// Removing an address from the set
uniqueAddresses.remove(Address("0xabc123"));
// Adding multiple addresses at once
val newAddresses = [Address("0xghi789"), Address("0xjkl012")];
uniqueAddresses.add_all(newAddresses);
// Printing the set
print("Unique addresses: %s".format(uniqueAddresses.to_text()));
}
example_set();
map<K,V>
A key-value pair collection type.
Basic map example
function basic_map_example() {
// Creating a simple map with literal values
val person = ["name": "Bob", "sibling": "Alice"];
// Accessing values by key
print("Name: %s".format(person["name"]));
print("Sibling: %s".format(person["sibling"]));
}
basic_map_example();
Rell does not prevent overwriting keys during map instantiation. If you specify the same key multiple times, the last value will be used:
// The second "base_url" value will overwrite the first one
val person = ["name": "Bob", "sibling": "Alice", "sibling": "Michelle"];
// Result: person["sibling"] will be "Michelle"
Complex map example
struct Balance {
amount: integer;
lastUpdated: timestamp;
}
function example_map() {
// Creating a map of blockchain addresses and their balances
val balances = map<text, Balance>();
// Adding balances to the map
balances["0xabc123"] = Balance(amount = 1000, lastUpdated = 1723472827);
balances["0xdef456"] = Balance(amount = 2000, lastUpdated = 1723472900);
// Accessing a balance by address
val aliceBalance = balances["0xabc123"];
print("Alice's balance: %d, Last updated: %d".format(aliceBalance.amount, aliceBalance.lastUpdated));
}
example_map();
Constructors
map<K,V>()- Creates a new empty map.map<K,V>(iterable<(K,V)>)- Creates a new map from list, set, or map key-value pairs.
Functions
| Function | Description |
|---|---|
.clear() | Removes all key-value pairs from the map. |
.contains(K): boolean | Checks if the map contains the specified key. |
.empty(): boolean | Returns true if the map is empty, otherwise returns false. |
.get(K): V | Get the value associated with the specified key. |
.get_or_null(key: K): V? | Returns null if the key isn't in the map; otherwise returns the associated value. |
.get_or_default(key: K, default_value: V2): V2 | Returns a default value if the key isn't in the map; otherwise returns the associated value. |
.remove_or_null(key: K): V? | Removes the specified key from the map. Returns null if the key isn't in the map. |
.put(K, V) | Adds/replaces a key-value pair in the map. |
.keys(): set<K> | Returns a copy of keys in the map. |
.put_all(map<K, V>) | Adds/replaces all key-value pairs from the given map to the map. |
.remove(K): V | Removes the key-value pair associated with the specified key from the map. |
.size(): integer | Returns the number of key-value pairs in the map. |
.to_text(): text | Returns a text representation of the map, e.g., '{x=123, y=456}'. |
.values(): list<V> | Returns a copy of values in the map. |
Special operators
[]- Accesses or modifies a value by key.in- Checks if a specified key exists in the map, returningtrueif it does.
Example:
struct Balance {
mutable amount: integer;
lastUpdated: timestamp;
}
function example_map() {
// Creating a map of blockchain addresses and their balances
val balances = map<text, Balance>();
// Using [] operator to add and modify balances
balances["0xabc123"] = Balance(amount = 1000, lastUpdated = 1723472827);
balances["0xdef456"] = Balance(amount = 2000, lastUpdated = 1723472937);
balances["0xghi789"] = Balance(amount = 3000, lastUpdated = 1723473427);
// Using [] operator to access balances
print("Alice's balance: %d".format(balances[0xabc123].amount));
print("Bob's balance: ${balances[0xdef456].amount}");
// Using [] operator to modify an existing balance
balances["0xabc123"].amount += 500;
print("Alice's updated balance: %d".format(balances[0xabc123].amount));
// Using 'in' operator to check for key existence
val addresses = ["0xabc123", "0xdef456", "0xjkl012", "0xmno345"];
for (address in addresses) {
if (address in balances) {
print("Address %s exists in the map with balance: %d".format(address, balances[address].amount));
} else {
print("Address %s does not exist in the map".format(address));
}
}
// Combining [] and 'in' operators for safe access
val charlieAddress = "0xghi789";
if (charlieAddress in balances) {
val charlieBalance = balances[charlieAddress];
print("Charlie's balance: %d, Last updated: %d".format(charlieBalance.amount, charlieBalance.lastUpdated));
}
// Using [] operator with get_or_default for safe access with a default value
val davidAddress = "0xjkl012";
val davidBalance = if (davidAddress in balances) balances[davidAddress] else Balance(0, block.timestamp);
print("David's balance: %d".format(davidBalance.amount));
// Printing updated balances
for ((address, balance) in balances) {
print("Address: ${address}, Updated Balance: %d".format(balance.amount));
}
}