Skip to main content

Complex types

Complex data types in the Rell programming language provide advanced features and capabilities beyond basic types like integers and strings. They allow for the representation and manipulation of structured data, enabling more sophisticated programming constructs.

Nullable types (T?)

A nullable type in Rell represents a value that might be absent or undefined. This allows for greater flexibility in handling optional data.

Key features

  • Entity attributes: Cannot be nullable to ensure data integrity.
  • Operators: Supports ?:, ?., and !! operators (similar to Kotlin).
  • Assignments:
    • Assign a T to T?, but not the reverse.
    • Assign null to T?, but not to T.
    • Tuple assignments allow (T) to be assigned to (T?), but not vice versa.
    • list<T> cannot be assigned to list<T?>.

Examples

// Nullable integer function
function f(): integer? {
return null;
}

function complex_types () {
val x: integer? = f(); // Nullable integer
val y = x; // y is also nullable integer

val i = y!!; // Non-null assertion, throws if y is null
val j = require(y); // Throws exception if y is null, else assigns y to j

val a = y ?: 456; // Elvis operator, assigns y if not null, else 456
val b = y ?: null; // b is nullable integer

val p = y!!; // Asserts y is non-null
val q = y?.to_hex(); // Safe call, returns null if y is null

if (x != null) {
val u = x; // type of "u" is "integer" - smart cast is applied to "x"
} else {
val v = x; // type of "v" is "integer?"
}
}

Tuple

Tuple offers a concise way to group multiple values of different types into a single, ordered entity.

Key features

  • Creation: Enclose elements within parentheses, separated by commas.
  • Access: By index or by name if named.
    • By index: Use numerical indices within square brackets (e.g., t[0]).
    • By name: Use field names for named tuples (e.g., named_tuple.x).
  • Type compatibility: Tuples are compatible only if elements are identical in order and type.
    • (x:integer, y:integer) and (a:integer,b:integer) aren't compatible.
    • (x:integer, y:integer) and (integer,integer) aren't compatible.
  • Unpacking: Deconstruct tuples into individual variables.

Examples

function complex_types (){
// Creating tuples
val single_number: (integer,) = (16,); // Single-element tuple
val user_tuple: (integer, text) = (26, "Bob"); // Tuple with integer and text
val named_tuple: (x: integer, y: integer) = (x=32, y=26); // Named tuple. Can access them as `named_tuple.x`, `named_tuple.y`)

// Accessing elements
val number = user_tuple[0]; // Access by index
val name = named_tuple.x; // Access by name

// Ignoring elements
val (_, s) = (123, 'Hello'); // s = 'Hello'
}

// Unpacking tuples
function foo() {
val t = (123, 'Hello');
val (n, s) = t; // n = 123, s = 'Hello'
}

// Looping through a list of tuples
function iterateTuples() {
val l: list<(integer, text)> = [(21, "test")];
for ((x, y) in l) {
print(x, y);
}
}

Range

Ranges represent sequences of numbers, often used in for loops and other scenarios requiring iteration.

Key features

  • Syntax: range(start: integer = 0, end: integer, step: integer = 1) - start-inclusive, end-exclusive.

  • Inclusive start, exclusive end: The generated sequence includes the start value but excludes the end value (as in Python).

  • Customizable step: The step argument controls the increment or decrement between consecutive values.

Examples

function ranges() {
// Range examples
val r1 = range(10); // 0 to 9
val r2 = range(5, 10); // 5 to 9
val r3 = range(5, 15, 4); // 5, 9, 13
val r4 = range(10, 5, -1); // 10 to 6
val r5 = range(10, 5, -3); // 10, 7

// Checking membership
if (3 in r1) {
print("3 is in the range");
}
}

Special operators

  • in - returns true if the value is in the range (taking step into account).

GTV

GTV is used to represent encoded arguments and results of remote operations and query calls. It can be a simple value, an array of values, or a string-keyed dictionary.

Queries return data in GTV format like:

query get_all_books() {
return (
.title = "Hello Chromia",
.author = "Chromia"
).to_gtv();
}

Key features

  • Compatibility: Some Rell types aren't GTV-compatible.
  • Functions: Provides methods to convert between GTV and other types.

Functions

FunctionDescription
gtv.from_json(text): gtvDecodes a gtv from a JSON string.
gtv.from_json(json): gtvDecodes a gtv from a json value.
gtv.from_bytes(byte_array): gtvDecodes a gtv value from its binary-encoded form.
gtv.from_bytes_or_null(byte_array): gtv?Same as gtv.from_bytes(byte_array), but returns null instead of an error if the byte array is not valid.
.to_json(): jsonConverts the gtv value to JSON.
.to_bytes(): byte_arrayConverts the gtv value to bytes.
.hash(): byte_arrayReturns a cryptographic hash of the gtv value, ensuring its integrity.

Functions available for all GTV-compatible types:

FunctionDescription
T.from_gtv(gtv): TDecode from a gtv.
T.from_gtv_pretty(gtv): TDecode from a pretty-encoded gtv.
.to_gtv(): gtvConvert to a gtv.
null.to_gtv()Returns the gtv equivalent of null.
.to_gtv_pretty(): gtvConvert to a pretty gtv.
.hash(): byte_arrayReturns a cryptographic hash of the gtv value, ensuring its integrity. Same as .to_gtv().hash().

Example

function foo() {
val g = [1, 2, 3].to_gtv(); // Converts a list to GTV
val l = list<integer>.from_gtv(g); // Converts GTV back to a list
print(l); // Outputs: [1, 2, 3]
print(g.hash()); // Prints the hash value of the GTV
}