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
toT?
, but not the reverse. - Assign
null
toT?
, but not toT
. - Tuple assignments allow
(T)
to be assigned to(T?)
, but not vice versa. list<T>
cannot be assigned tolist<T?>
.
- Assign a
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
).
- By index: Use numerical indices within square brackets (e.g.,
- 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 theend
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
- returnstrue
if the value is in the range (takingstep
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
Function | Description |
---|---|
gtv.from_json(text): gtv | Decodes a gtv from a JSON string. |
gtv.from_json(json): gtv | Decodes a gtv from a json value. |
gtv.from_bytes(byte_array): gtv | Decodes 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(): json | Converts the gtv value to JSON. |
.to_bytes(): byte_array | Converts the gtv value to bytes. |
.hash(): byte_array | Returns a cryptographic hash of the gtv value, ensuring its integrity. |
GTV-related functions
Functions available for all GTV-compatible types:
Function | Description |
---|---|
T.from_gtv(gtv): T | Decode from a gtv . |
T.from_gtv_pretty(gtv): T | Decode from a pretty-encoded gtv . |
.to_gtv(): gtv | Convert to a gtv . |
null.to_gtv() | Returns the gtv equivalent of null. |
.to_gtv_pretty(): gtv | Convert to a pretty gtv . |
.hash(): byte_array | Returns 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
}