Skip to main content

Types

Simple types

Boolean

val using_rell = true;
function foo() {
if(using_rell) print("Awesome!");
}

Integer

val user_age : integer = 26;
  • integer.MIN_VALUE = minimum value (-2^63)

  • integer.MAX_VALUE = maximum value (2^63-1)

  • integer(s: text, radix: integer = 10) - parse a signed string representation of an integer, fail if invalid

  • integer(decimal): integer - converts a decimal to an integer, rounding towards 0 (5.99 becomes 5, -5.99 becomes -5), throws an exception if the resulting value is out of range

  • integer.from_text(s: text, radix: integer = 10): integer - same as integer(text, integer)

  • integer.from_hex(text): integer - parse an unsigned HEX representation

  • .abs(): integer - absolute value

  • .max(integer): integer - maximum of two values

  • .max(decimal): decimal - maximum of two values (converts this integer to decimal)

  • .min(integer): integer - minimum of two values

  • .min(decimal): decimal - minimum of two values (converts this integer to decimal)

  • .to_text(radix: integer = 10) - convert to a signed string representation

  • .to_hex(): text - convert to an unsigned HEX representation

  • .sign(): integer - returns -1, 0, or 1 depending on the sign

big_integer

val bi : big_integer = 9223372036854775832L;

You must add L to the value to specify that the value must be interpreted and treated as a big_integer.

  • big_integer.PRECISION: integer - the maximum number of digits (131072)
  • big_integer.MIN_VALUE: big_integer = minimum value (-(10^131072)+1)
  • big_integer.MAX_VALUE: big_integer = maximum value ((10^131072)-1)
  • big_integer(integer): big_integer - creates a big_integer from integer
  • big_integer(text): big_integer - creates a big_integer from a decimal string representation, possibly with a sign. It fails if the string is not a valid decimal number.
  • big_integer.to_integer(): integer - converts big_integer to integer. It fails if the value is out of range.
  • integer.to_big_integer(): big_integer - converts integer to big_integer
  • big_integer.to_decimal(): decimal - converts big_integer to decimal
  • decimal.to_big_integer(): big_integer - converts decimal to big_integer. It truncates the fractional part.
  • big_integer.to_bytes(): byte_array - converts a signed (positive or negative) big_integer to a byte_array. For example, the value 0x7FL is encoded as x'7F', but 0x80L is encoded as x'0080' because a sign needs an extra byte.
  • big_integer.from_bytes(byte_array): big_integer - converts a byte_array produced by .to_bytes() back to big_integer.
  • big_integer.to_bytes_unsigned(): byte_array - converts a positive big_integer to a byte_array. It does not add a sign bit and fails if a negative value is provided. For example, 0x80L is encoded as x'80', 0xFFL as x'FF', and 0x100L as x'0100'.
  • big_integer.from_bytes_unsigned(byte_array): big_integer - converts a byte_array produced by .to_bytes_unsigned() back to big_integer.
  • .abs(): big_integer - absolute value
  • .max(big_integer): big_integer - maximum of two values
  • .max(decimal): decimal - maximum of two values (converts this big_integer to decimal)
  • .min(big_integer): big_integer - minimum of two values
  • .min(decimal): decimal - minimum of two values (converts this big_integer to decimal)
  • .to_text(): text - converts to a decimal string representation.
  • .from_text(text): big_integer - converts from a decimal string representation
  • .to_text(radix: integer): text - converts to a string representation with a specific base (radix, from 2 to 36)
  • .from_text(text, radix: integer): big_integer - converts from a string representation with a specific base (radix, from 2 to 36)
  • .to_hex(): text - convert to an unsigned HEX representation. It supports positive and negative numbers. For example, (127L).to_hex() returns 7f.
  • .from_hex(text): big_integer - parse an unsigned HEX representation. It supports positive and negative numbers. For example, (7f).from_hex() returns 127L.
  • .sign(): big_integer - returns -1, 0, or 1 depending on the sign

Decimal

Represent a real number.

val approx_pi : decimal = 3.14159;
val scientific_value : decimal = 55.77e-5;

It's not a normal floating-point type found in many other languages (like float and double in C/C++/Java):

  • decimal type is accurate when working with numbers within its range. All decimal numbers (results of decimal operations) are implicitly rounded to 20 decimal places. For instance, decimal('1E-20') returns a non-zero, while decimal('1E-21') returns a zero value.

  • Numbers get stored in a decimal form, not binary form, so conversions to and from a string are lossless (except when rounding occurs if there are more than 20 digits after the point).

  • Floating-point types allow storing much smaller numbers, like 1E-300; decimal can only store 1E-20, but not a smaller nonzero number.

  • Operations on decimal numbers may be considerably slower than integer operations (at least ten times slower for the same integer numbers).

  • Large decimal numbers may require a lot of space: ~0.41 bytes per decimal digit (~54KiB for 1E+131071) in memory and ~0.5 bytes per digit in a database.

  • Internally, the type java.lang.BigDecimal gets used in the interpreter, and NUMERIC in SQL.

  • In the code, one can use decimal literals:

    123.456
    0.123
    .456
    33E+10
    55.77e-5

    Such numbers have a decimal type. Simple numbers without a decimal point and exponent, like 12345, have integer type.

  • decimal.PRECISION: integer - the maximum number of decimal digits (131072 + 20)

  • decimal.SCALE: integer - the maximum number of decimal digits after the decimal point (20)

  • decimal.INT_DIGITS: integer - the maximum number of digits before the decimal point (131072)

  • decimal.MIN_VALUE: decimal - the smallest nonzero absolute value that a decimal can store

  • decimal.MAX_VALUE: decimal - the largest value that you can store in a decimal (1E+131072 - 1)

  • decimal(integer): decimal - converts an integer to decimal

  • decimal(big_integer): decimal - converts a big_integer to decimal

  • decimal(text): decimal - converts a text representation of a number to decimal

  • .abs(): decimal - absolute value

  • .ceil(): decimal - ceiling value: rounds 1.0 to 1.0, 1.00001 to 2.0, -1.99999 to -1.0, etc.

  • .floor(): decimal - floor value: rounds 1.0 to 1.0, 1.9999 to 1.0, -1.0001 to -2.0, etc.

  • .min(decimal): decimal - minimum of two values

  • .max(decimal): decimal - maximum of two values

  • .round(scale: integer = 0): decimal - rounds to a specific number of decimal places to a closer value. For example, round(2.49) = 2.0, round(2.50) = 3.0, round(0.12345, 3) = 0.123. Negative scales allowed: round(12345, -3) = 12000.

  • .sign(): integer - returns -1, 0, or 1 depending on the sign

  • .to_integer(): integer - converts a decimal to an integer, rounding towards 0. For example, (5.99 becomes 5, -5.99 becomes -5).

  • .to_text(scientific: boolean = false): text - convert to string

Text

Textual value. Same as string type in some other languages.

val placeholder = "Lorem ipsum donor sit amet";
function foo() {
print(placeholder.size()); // 26
print(placeholder.empty()); // false
}
  • text.from_bytes(byte_array, ignore_invalid: boolean = false) - if ignore_invalid is false, throws an exception when the byte array is not a valid UTF-8 encoded string, otherwise replaces invalid characters with a placeholder.

  • .empty(): boolean - returns true if the text is empty, otherwise returns false

  • .size(): integer - returns the number of characters.

  • .compare_to(text): integer - returns 0 if texts match, otherwise a positive or negative value

  • .starts_with(text): boolean - returns true if it starts with the input text, otherwise returns false

  • .ends_with(text): boolean - returns true if it ends with the input text, otherwise returns false

  • .contains(text): boolean - return true if contains the given substring, otherwise returns false

  • .index_of(text): integer - returns position of input text and -1 if a substring isn't found

  • .index_of(text, integer): integer - same as .index_of(text) but starting search from given position

  • .last_index_of(text[, start: integer]): integer - returns -1 if a substring isn't found (as in Java)

  • .sub(start: integer[, end: integer]): text - get a substring (start-inclusive, end-exclusive)

  • .repeat(n: integer): text - returns the text repeated "n" times

  • .replace(old: text, new: text) - replaces substring with new text

  • .reversed(): text - returns a reversed copy of the text

  • .upper_case(): text - converts the text to uppercase letters

  • .lower_case(): text - converts the text to lowercase letters

  • .split(text): list<text> - strictly split by a separator (not a regular expression)

  • .trim(): text - remove leading and trailing whitespace

  • .matches(text): boolean - returns true if it matches a regular expression

  • .to_bytes(): byte_array - convert to a UTF-8 encoded byte array

  • .char_at(integer): integer - get a 16-bit code of a character

  • .format(...) - formats a string (as in Java). For example, 'My name is <%s>'.format('Bob') - returns 'My name is <Bob>'

You can use most of these text functions within at-expressions where they're translated to their SQL equivalents.

Special operators:

  • +: concatenation
  • []: character access (returns single-character text)

byte_array

val user_pubkey: byte_array = x "0373599a61cc6b3bc02a78c34313e1737ae9cfd56b9bb24360b437d469efdf3b15";
function foo() {
print(user_pubkey.to_base64()); //A3NZmmHMazvAKnjDQxPhc3rpz9Vrm7JDYLQ31Gnv3zsV
}
  • byte_array(text) - creates a byte_array from a HEX string, for example '1234abcd'

  • byte_array.from_hex(text): byte_array - same as byte_array(text)

  • byte_array.from_base64(text): byte_array - creates a byte_array from a Base64 string

  • byte_array.from_list(list<integer>): byte_array - converts list to a byte array; values must be 0 - 255

  • .empty(): boolean - returns true if the text is empty, otherwise returns false

  • .repeat(n: integer): byte_array - returns the byte array repeated "n" times

  • .reversed(): byte_array - returns a reversed copy of the byte array

  • .size(): integer - returns the number of characters

  • .sub(start: integer[, end: integer]): byte_array - sub-array (start-inclusive, end-exclusive)

  • .to_hex(): text - returns a HEX representation of the byte array, for example, '1234abcd'

  • .to_base64(): text - returns a Base64 representation of the byte array

  • .to_list(): list<integer> - list of values 0 - 255

  • .sha256(): byte_array - returns the sha256 digest as a byte_array

You can use most of these byte array functions within at-expressions, where they're translated to their SQL equivalents.

Special operators:

  • +: concatenation
  • []: element access

ROWID

The primary key of a database record, a 64-bit integer, supports only comparison operations.

  • rowid(integer): rowid - converts an integer to rowid. The value must not be negative.
  • rowid.to_integer(): integer - returns the integer value of the rowid

JSON

Stored in PostgreSQL as JSON type and gets parsed to text;

val json_text = '{ "name": "Alice" }';
val json_value: json = json(json_text);
function foo() {
print(json_value);
}
  • json(text) - create a json value from a string; fails if not a valid JSON string

  • .to_text(): text - convert to string

Unit

No value; can't use it explicitly. Equivalent to [unit]{.title-ref} type in Kotlin.

Null

Type of null expression; can't use it explicitly.

Simple type aliases

  • pubkey = byte_array
  • name = text
  • timestamp = integer
  • tuid = text

Complex types

Entity

The entity represents a database object. An example to visualize how to maintain relations between objects is as follows:

entity user {
key pubkey;
index name;
}
entity address {
key pubkey;
street: text;
}

entity residence {
key user, address;
}
//both queries will result lists of records, as it is a many-to-many relationships
//they might be of different sizes. The constraint is that the composite of the two keys are unique.
function get_addresses(user): list<address> = residence @* { user }.address;
function get_users(address): list<user> = residence @* { address }.user;

is many-to-many relations, a user can have more than one residence. You can implement many-to-one relations by changing the key:

entity residence {
key user;
index address;
}
//There is a unique constraint on the user key.
function get_addresses(user): list<address> = residence @* { user }.address; //will only return zero or one addresses
function get_users(address): list<user> = residence @* { address }.user; // will return zero to n users

And for completeness, one-to-many would be reverse of many-to-one:

entity residence {
index user;
key address;
}

It also works without making the address an entity, for example:

entity user { key name; }

// allow user to have many tags
entity user_tag {
key user, tag: text;
}

and one-to-one (optional) as:

entity residence {
key user;
key address; //make sure that address can be claimed by at most one user
}

There is a unique constraint on the user key and the address key.

function get_addresses(user): list<address> = residence @* { user }.address; //will only return zero or one addresses
function get_users(address): list<user> = residence @* { address }.user; // will only return zero to one users

Struct

A struct is similar to an entity, but its instances exist in memory, not in a database.

struct user {
name: text;
address: text;
mutable balance: integer = 0;
}

Functions available for all struct types:

  • T.from_bytes(byte_array): T - decode from a binary-encoded gtv (same as T.from_gtv(gtv.from_bytes(x)))

  • T.from_gtv(gtv): T - decode from a gtv

  • T.from_gtv_pretty(gtv): T - decode from a pretty-encoded gtv

  • .to_bytes(): byte_array - encode in binary format (same as .to_gtv().to_bytes())

  • .to_gtv(): gtv - convert to a gtv

  • .to_gtv_pretty(): gtv - convert to a pretty gtv

ENUM

enum account_type {
single_key_account,
multi_sig_account
}

entity account {
key id: byte_array;
mutable account_type;
mutable args: byte_array;
}

Assuming T is an enum type:

  • T.values(): list<T> - returns all values of the enum in the order of declaration
  • T.value(text): T - finds a value by name, gives an exception if not found
  • T.value(integer): T - finds a value by index, gives an exception if not found

ENUM value properties

  • .name: text - the name of the enum value
  • .value: integer - the numeric value (index) associated with the enum value

T? - nullable type

// It uses the user struct that was defined earlier.
val nonexistent_user = user @? { .name == "Nonexistent Name" };

// Should be in local scope
require_not_empty(nonexistent_user) // Throws exception because user doesn't exist
  • Entity attributes can't be nullable
  • You can use it with almost any type (except nullable, unit, null)
  • You can't apply normal operations of the underlying type
  • It supports ?:, ?. and !! operators (like in Kotlin)

Compatibility with other types:

  • You can assign a value of type T to a variable of type T?, but not the other way round
  • You can assign null to a variable of type T?, but not to a variable of type T
  • You can assign a value of type (T) (tuple) to a variable of type (T?)
  • You can't assign a value of type list<T> to a variable of type list<T?>

Allowed operations:

  • Null comparison: x == null, x != null
  • ?? - null check operator: x?? is equivalent to x != null
  • !! - null assertion operator: x!! returns value of x if x is not null, otherwise throws an exception
  • ?: - Elvis operator: x ?: y means x if x isn't null, otherwise y
  • ?. - safe access: x?.y results in x.y if x isn't null and null otherwise; similarly, x?.y() either evaluates and returns x.y() or returns null
  • require(x), require_not_empty(x): throws an exception if x is null, otherwise returns value of x

Examples:

function f(): integer? { ... }

val x: integer? = f(); // type of "x" is "integer?"
val y = x; // type of "y" is "integer?"

val i = y!!; // type of "i" is "integer"
val j = require(y); // type of "j" is "integer"

val a = y ?: 456; // type of "a" is "integer"
val b = y ?: null; // type of "b" is "integer?"

val p = y!!; // type of "p" is "integer"
val q = y?.to_hex(); // type of "q" is "text?"

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

Examples:

  • val single_number : (integer,) = (16,) - one value
  • val invalid_tuple = (789) - not a tuple (no comma)
  • val user_tuple: (integer, text) = (26, "Bob") - two values
  • val named_tuple : (x: integer, y: integer) = (x=32, y=26) - named fields (can access them as named_tuple.x, named_tuple.y)
  • (integer, (text, boolean)) - nested tuple

Tuple types are compatible only if names and types of fields are the same:

  • (x:integer, y:integer) and (a:integer,b:integer) aren't compatible.
  • (x:integer, y:integer) and (integer,integer) aren't compatible.

Reading tuple fields:

  • t[0], t[1] - by index
  • t.a, t.b - by name (for named fields)

Unpacking tuples:

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

Works for arbitrarily nested tuples:

val (n, (p, (x, y), q)) = calculate();

You can use a special symbol _ to ignore a tuple element:

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

You can specify variable types explicitly:

val (n: integer, s: text) = (123, 'Hello');

You can use unpacking in a loop:

function foo() {
val l: list < (integer, text) > = [(21, "test")];
for ((x, y) in l) {
print(x, y);
}
}

The function creates a list of tuples containing an integer and a text value and then iterates over the list using a for loop to print out the values of each tuple.

Range

You can use it in for statement:

for(count in range(10)){
print(count); // prints out 0 to 9
}

range(start: integer = 0, end: integer, step: integer = 1) -start-inclusive, end-exclusive (as in Python):

  • range(10) - a range from 0 (inclusive) to 10 (exclusive)
  • range(5, 10) - from 5 to 10
  • range(5, 15, 4) - from 5 to 15 with step 4, i. e. [5, 9, 13]
  • range(10, 5, -1) - produces [10, 9, 8, 7, 6]
  • range(10, 5, -3) - produces [10, 7]

Special operators:

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

GTV

A type used to represent encoded arguments and results of remote operation and query calls. It may be a simple value (integer, string, byte array), an array of values, or a string-keyed dictionary.

Some Rell types aren't Gtv-compatible. Values of such types can't get converted to/from gtv, and you can't use the types as types of operation/query parameters or result types.

Rules of GTV-compatibility

  • the range isn't GTV-compatible

  • a complex type isn't Gtv-compatible if a type of its component is not Gtv-compatible

Methods

  • 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 from a 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 a valid gtv
  • .to_json(): json - converts to JSON
  • .to_bytes(): byte_array - converts to bytes
  • .hash(): byte_array - returns a cryptographic hash of the value

gtv-related functions

Functions available for all Gtv-compatible types:

  • 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 value (same as .to_gtv().hash())

Examples

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

Collection types

Rell supports the following collection types:

  • list<T> - an ordered list
  • set<T> - an unordered set. It contains no duplicates
  • map<K,V> - a key-value map

Collection types are mutable. You can add or remove elements dynamically. You can use only a non-mutable type as a map key or a set element.

Mutable collection types

  • Collection types (list, set, map) - always.
  • 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 a type of an element is mutable.

Creating collections

function foo() {
// list
val l1 = [1, 2, 3, 4, 5];
val l2 = list < integer > ();

// set
val s = set < integer > ();

// map
val m1 = ['Bob': 123, 'Alice': 456];
val m2 = map < text, integer > ();
}

list<T>

A list is an ordered collection type that accepts duplication of elements.

// It uses the message list that must have been defined earlier.
var messages = message @* { } ( @sort timestamp = .timestamp );
messages.sort_by { .timestamp }; // Sorts the messages list by timestamp
messages.add(new_message); // Adds a new message to the messages list

Constructors

  • list<T>() - a new empty list

  • list<T>(list<T>) - a copy of the given list (list of subtype is acceptable as well)

  • list<T>(set<T>) - a copy of the given set (set of subtype is acceptable)

Methods

  • .add(T): boolean - adds an element to the end, always returns true

  • .add(pos: integer, T): boolean - inserts an element at a position, always returns true

  • .add_all(list<T>): boolean

  • .add_all(set<T>): boolean

  • .add_all(pos: integer, list<T>): boolean

  • .add_all(pos: integer, set<T>): boolean

  • .clear()

  • .contains(T): boolean

  • .contains_all(list<T>): boolean

  • .contains_all(set<T>): boolean

  • .empty(): boolean

  • .index_of(T): integer - returns -1 if the element isn't found

  • .remove(T): boolean - removes the first occurrence of the value, returns true if found

  • .remove_all(list<T>): boolean

  • .remove_all(set<T>): boolean

  • .remove_at(pos: integer): T - removes an element at a given position

  • .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

  • .reverse() - reverses the list in place and 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 this list, returns nothing (name is _sort, because sort is a keyword in Rell)

  • .sorted(): list<T> - returns a sorted copy of this list

  • .to_text(): text - returns e. g. '[1, 2, 3, 4, 5]'

  • .sub(start: integer[, end: integer]): list<T> - returns a sub-list (start-inclusive, end-exclusive)

Special operators

  • [] - element access (read/modify)
  • in - returns true if the value is in the list

set<T>

Unordered collection type. It doesn't allow duplication.

// It uses the user set that must have been defined earlier.
var my_classmates = set<user>();
my_classmates.add(alice); // Adds alice to my_classmates set. Returns true.
my_classmates.add(alice); // Adds alice to my_classmates set again. Returns false because alice is already present.

Constructors

  • set<T>() - a new empty set

  • set<T>(set<T>) - a copy of the given set (set of subtype is acceptable as well)

  • set<T>(list<T>) - a copy of the given list (with duplicates removed)

Methods

  • .add(T): boolean - if the element isn't in the set, adds it and returns true

  • .add_all(list<T>): boolean - adds all elements, returns true if at least one added

  • .add_all(set<T>): boolean - adds all elements, returns true if at least one added

  • .clear()

  • .contains(T): boolean

  • .contains_all(list<T>): boolean

  • .contains_all(set<T>): boolean

  • .empty(): boolean

  • .remove(T): boolean - removes the element, returns true if found

  • .remove_all(list<T>): boolean - returns true if at least one removed

  • .remove_all(set<T>): boolean - returns true if at least one removed

  • .size(): integer

  • .sorted(): list<T> - returns a sorted copy of this set (as a list)

  • .to_text(): text - returns e. g. '[1, 2, 3, 4, 5]'

Special operators

  • in - returns true if the value is in the set

map<K,V>

A key/value pair collection type.

function foo() {
var dictionary = map<text, text>();
dictionary["Mordor"] = "A place where one does not simply walk into"; // Sets the value of the "Mordor" key to "A place where one does not simply walk into"
}

Constructors

map<K,V>() - a new empty map

map<K,V>(map<K,V>) - a copy of the given map (map of subtypes is acceptable as well)

Methods

  • .clear()

  • .contains(K): boolean

  • .empty(): boolean

  • .get(K): V - get value by key (same as [])

  • get_or_null(key: K): V? - returns null if the key isn't in the map; otherwise returns the associated value. Unlike .get(K), which gives an exception in such a case.

  • .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. The type of the default value can be a V super type. For instance, the default value can be V? or null, even if V isn't nullable.

  • .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

  • .keys(): set<K> - returns a copy of keys

  • .put_all(map<K, V>) - adds/replaces all key-value pairs from the given map

  • .remove(K): V - removes a key-value pair (fails if the key isn't in the map)

  • .size(): integer

  • .to_text(): text - returns e. g. '{x=123, y=456}'

  • .values(): list<V> - returns a copy of values

Special operators

  • [] - get/set value by key
  • in - returns true if a key is in the map

Virtual types

A reduced data structure with Merkle tree. Type virtual<T> supports the following types of T:

  • list<*>
  • set<*>
  • map<text, *>
  • struct
  • tuple

T elements constraints

Additionally, types of all internal elements of T must satisfy the following constraints:

  • must be Gtv-compatible
  • for a map type, the key type must be text (i. e. map<text, *>)

Virtual types operations

  • member access: [] for list and map, .name for struct and tuple
  • .to_full(): T - converts the virtual value to the original value if the value is full (all internal elements are present), otherwise throws an exception
  • .hash(): byte_array - returns the hash of the value, which is the same as the hash of the original value.
  • virtual<T>.from_gtv(gtv): virtual<T> - decodes a virtual value from a Gtv.

Features of virtual<T>

  • it's immutable
  • reading a member of type list<*>, map<*,*>, struct or tuple returns a value of the corresponding virtual type, not of the actual member type
  • can't get converted to Gtv, so can't use it as a return type of a query

Example

struct rec { t: text; s: integer; }

operation op(recs: virtual<list<rec>>) {
for (rec in recs) { // type of "rec" is "virtual<rec>", not "rec"
val full = rec.to_full(); // type of "full" is "rec", fails if the value is not full
print(full.t);
}
}

Virtual<list<T>>

  • virtual<list<T>>.from_gtv(gtv): virtual<list<T>> - decodes a Gtv

  • .empty(): boolean

  • .get(integer): virtual<T> - returns an element, same as []

  • .hash(): byte_array

  • .size(): integer

  • .to_full(): list<T> - converts to the original value, fails if the value isn't full

  • .to_text(): text - returns a text representation

Special operators

  • [] - element read, returns virtual<T> (or just T for simple types)
  • in - returns true if the given integer index is present in the virtual list

Virtual<set<T>>

  • virtual<set<T>>.from_gtv(gtv): virtual<set<T>> - decodes a Gtv

  • .empty(): boolean

  • .hash(): byte_array

  • .size(): integer

  • .to_full(): set<T> - converts to the original value, fails if the value isn't full

  • .to_text(): text - returns a text representation

Special operators

  • in - returns true if the given value is present in the virtual set; the type of the operand is virtual<T>> (or just T for simple types)

Virtual<map<K,V>>

  • virtual<map<K,V>>.from_gtv(gtv): virtual<map<K,V>> - decodes a Gtv

  • .contains(K): boolean - same as the operator in

  • .empty(): boolean

  • .get(K): virtual<V> - same as the operator []

  • .hash(): byte_array

  • .keys(): set<K> - returns a copy of keys

  • .size(): integer

  • .to_full(): map<K,V> - converts to the original value, fails if the value isn't full

  • .to_text(): text - returns a text representation

  • .values(): list<virtual<V>> - returns a copy of values (if V is a simple type, returns list<V>)

Special operators

  • [] - get value by key, fails if not found, returns virtual<V> (or just V for simple types)
  • in - returns true if a key is in the map

virtual<struct>

  • virtual<R>.from_gtv(gtv): R - decodes a Gtv

  • .hash(): byte_array

  • .to_full(): R - converts to the original value, fails if the value is not full


Iterables

Iterable value is a new concept for uniform sequence processing. The compiler has an internal type iterable<T>, which cannot be used explicitly in code, but different types can be used as iterable<T>:

  • range: iterable<integer>
  • list<T>: iterable<T>
  • set<T>: iterable<T>
  • map<K,V>: iterable<(K,V)>
  • virtual<list<T>>: iterable<T>
  • virtual<set<T>>: iterable<T>
  • virtual<map<K,V>>: iterable<(K,V)>

Iterable types can be used in the:

  • for statement: for (x in range(10)) { ... }
  • collection-at expression: [1,2,3] @* {} ( ... )
  • list/set/map constructor: set([1,2,3])

Practical uses: range type can be used in collection-at and list/set/map constructor:

val squares = range(10) @* {} ( $ * $ );
val naturals = set(range(1, 10));

For map<K,V> type, a new constructor accepting iterable<(K,V)> is added, which turns a collection of tuples into a map:

val tuples = [(1,'A'), (2,'B'), (3,'C')];
val m = map(tuples); // "m" is map<integer,text>

A map can be turned into a list of tuples:

val l = list(m);                            // "l" is list<(integer,text)>

Subtypes

If type B is a subtype of type A, a value of type B can get assigned to a type A variable (or passed as a parameter of type A).

  • T is a subtype of T?.
  • null is a subtype of T?.
  • (T,P) is a subtype of (T?,P?), (T?,P) and (T,P?).