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 invalidinteger(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 rangeinteger.from_text(s: text, radix: integer = 10): integer
- same asinteger(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 thisinteger
todecimal
).min(integer): integer
- minimum of two values.min(decimal): decimal
- minimum of two values (converts thisinteger
todecimal
).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
, or1
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 abig_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 abig_integer
frominteger
big_integer(text): big_integer
- creates abig_integer
from adecimal
string representation, possibly with a sign. It fails if the string is not a valid decimal number.big_integer.to_integer(): integer
- convertsbig_integer
tointeger
. It fails if the value is out of range.integer.to_big_integer(): big_integer
- convertsinteger
tobig_integer
big_integer.to_decimal(): decimal
- convertsbig_integer
todecimal
decimal.to_big_integer(): big_integer
- convertsdecimal
tobig_integer
. It truncates the fractional part.big_integer.to_bytes(): byte_array
- converts a signed (positive or negative)big_integer
to abyte_array
. For example, the value0x7FL
is encoded asx'7F'
, but0x80L
is encoded asx'0080
' because a sign needs an extra byte.big_integer.from_bytes(byte_array): big_integer
- converts abyte_array
produced by.to_bytes()
back tobig_integer
.big_integer.to_bytes_unsigned(): byte_array
- converts a positivebig_integer
to abyte_array
. It does not add a sign bit and fails if a negative value is provided. For example,0x80L
is encoded asx'80'
,0xFFL
asx'FF'
, and0x100L
asx'0100'
.big_integer.from_bytes_unsigned(byte_array): big_integer
- converts abyte_array
produced by.to_bytes_unsigned()
back tobig_integer
..abs(): big_integer
- absolute value.max(big_integer): big_integer
- maximum of two values.max(decimal): decimal
- maximum of two values (converts thisbig_integer
todecimal
).min(big_integer): big_integer
- minimum of two values.min(decimal): decimal
- minimum of two values (converts thisbig_integer
todecimal
).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()
returns7f
..from_hex(text): big_integer
- parse an unsigned HEX representation. It supports positive and negative numbers. For example,(7f).from_hex()
returns127L
..sign(): big_integer
- returns-1
,0
, or1
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, whiledecimal('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 store1E-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, andNUMERIC
in SQL.In the code, one can use decimal literals:
123.456
0.123
.456
33E+10
55.77e-5Such numbers have a
decimal
type. Simple numbers without a decimal point and exponent, like 12345, haveinteger
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 adecimal
can storedecimal.MAX_VALUE: decimal
- the largest value that you can store in adecimal
(1E+131072 - 1)decimal(integer): decimal
- converts aninteger
todecimal
decimal(big_integer): decimal
- converts abig_integer
todecimal
decimal(text): decimal
- converts a text representation of a number todecimal
.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
, or1
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)
- ifignore_invalid
isfalse
, 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 thetext
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-charactertext
)
byte_array
val user_pubkey: byte_array = x "0373599a61cc6b3bc02a78c34313e1737ae9cfd56b9bb24360b437d469efdf3b15";
function foo() {
print(user_pubkey.to_base64()); //A3NZmmHMazvAKnjDQxPhc3rpz9Vrm7JDYLQ31Gnv3zsV
}
byte_array(text)
- creates abyte_array
from a HEX string, for example'1234abcd'
byte_array.from_hex(text): byte_array
- same asbyte_array(text)
byte_array.from_base64(text): byte_array
- creates abyte_array
from a Base64 stringbyte_array.from_list(list<integer>): byte_array
- converts list to a byte array; values must be 0 - 255.empty(): boolean
- returns true if thetext
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 aBase64
representation of the byte array.to_list(): list<integer>
- list of values 0 - 255.sha256(): byte_array
- returns the sha256 digest as abyte_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 aninteger
torowid
. 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 ajson
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-encodedgtv
(same asT.from_gtv(gtv.from_bytes(x))
)T.from_gtv(gtv): T
- decode from agtv
T.from_gtv_pretty(gtv): T
- decode from a pretty-encodedgtv
.to_bytes(): byte_array
- encode in binary format (same as.to_gtv().to_bytes()
).to_gtv(): gtv
- convert to agtv
.to_gtv_pretty(): gtv
- convert to a prettygtv
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 theenum
in the order of declarationT.value(text): T
- finds a value by name, gives an exception if not foundT.value(integer): T
- finds a value by index, gives an exception if not found
ENUM value properties
.name: text
- the name of theenum
value.value: integer
- the numeric value (index) associated with theenum
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 typeT?
, but not the other way round - You can assign
null
to a variable of typeT?
, but not to a variable of typeT
- 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 typelist<T?>
Allowed operations:
- Null comparison:
x == null
,x != null
??
- null check operator:x??
is equivalent tox != null
!!
- null assertion operator:x!!
returns value ofx
ifx
is notnull
, otherwise throws an exception?:
- Elvis operator:x ?: y
meansx
ifx
isn'tnull
, otherwisey
?.
- safe access:x?.y
results inx.y
ifx
isn'tnull
andnull
otherwise; similarly,x?.y()
either evaluates and returnsx.y()
or returnsnull
require(x)
,require_not_empty(x)
: throws an exception ifx
isnull
, otherwise returns value ofx
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 valueval invalid_tuple = (789)
- not a tuple (no comma)val user_tuple: (integer, text) = (26, "Bob")
- two valuesval named_tuple : (x: integer, y: integer) = (x=32, y=26)
- named fields (can access them asnamed_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 indext.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 10range(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
- returnstrue
if the value is in the range (takingstep
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-compatiblea complex type isn't Gtv-compatible if a type of its component is not Gtv-compatible
Methods
gtv.from_json(text): gtv
- decodes agtv
from a JSON stringgtv.from_json(json): gtv
- decodes agtv
from ajson
valuegtv.from_bytes(byte_array): gtv
- decodes agtv
from a binary-encoded formgtv.from_bytes_or_null(byte_array): gtv?
- same asgtv.from_bytes(byte_array)
, but returnsnull
instead of an error if the byte array is not a validgtv
.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 agtv
T.from_gtv_pretty(gtv): T
- decode from a pretty-encodedgtv
.to_gtv(): gtv
- convert to agtv
null.to_gtv()
- Returns the gtv equivalent of null.to_gtv_pretty(): gtv
- convert to a prettygtv
.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 listset<T>
- an unordered set. It contains no duplicatesmap<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 listlist<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 returnstrue
.add(pos: integer, T): boolean
- inserts an element at a position, always returnstrue
.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, returnstrue
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
, becausesort
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
- returnstrue
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 setset<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 returnstrue
.add_all(list<T>): boolean
- adds all elements, returnstrue
if at least one added.add_all(set<T>): boolean
- adds all elements, returnstrue
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, returnstrue
if found.remove_all(list<T>): boolean
- returnstrue
if at least one removed.remove_all(set<T>): boolean
- returnstrue
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
- returnstrue
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 aV
super type. For instance, the default value can beV?
ornull
, even ifV
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 keyin
- returnstrue
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 betext
(i. e.map<text, *>
)
Virtual types operations
- member access:
[]
forlist
andmap
,.name
forstruct
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, returnsvirtual<T>
(or justT
for simple types)in
- returnstrue
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
- returnstrue
if the given value is present in the virtual set; the type of the operand isvirtual<T>>
(or justT
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 operatorin
.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 (ifV
is a simple type, returnslist<V>
)
Special operators
[]
- get value by key, fails if not found, returnsvirtual<V>
(or justV
for simple types)in
- returnstrue
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 ofT?
.null
is a subtype ofT?
.(T,P)
is a subtype of(T?,P?)
,(T?,P)
and(T,P?)
.