General Language Features¶
Types¶
Simple types:¶
boolean
integer
text
byte_array
json
unit
(no value; cannot be used explicitly)null
(type ofnull
expression; cannot be used explicitly)
Simple type aliases:
pubkey
=byte_array
name
=text
timestamp
=integer
tuid
=text
Complex types:¶
- class
T?
- nullable typerecord
- tuple:
(T1, ..., Tn)
list<T>
set<T>
map<K,V>
range
(can be used infor
statement)GTXValue
- used to encode parameters and results of operations and queries
Nullable type¶
- Class attributes cannot be nullable.
- Can be used with almost any type (except nullable,
unit
,null
). - Nullable nullable (
T??
is not allowed). - Normal operations of the underlying type cannot be applied directly.
- Supports
?:
,?.
and!!
operators (like in Kotlin).
Compatibility with other types:
- Can assign a value of type
T
to a variable of typeT?
, but not the other way round. - Can assign
null
to a variable of typeT?
, but not to a variable of typeT
. - Can assign a value of type
(T)
(tuple) to a variable of type(T?)
. - Cannot assign a value of type
list<T>
to a variable of typelist<T?>
.
Allowed operations:
- Null comparison:
x == null
,x != null
. ?:
- Elvis operator:x ?: y
meansx
ifx
is notnull
, otherwisey
.?.
- safe access:x?.y
results inx.y
ifx
is notnull
andnull
otherwise.- Operator
?.
can be used with function calls, e. g.x?.upperCase()
. !!
- null check operator:x!!
returns value ofx
ifx
is notnull
, otherwise throws an exception.require(x)
,requireNotEmpty(x)
: throws an exception ifx
isnull
, otherwise returns value ofx
.
Examples:
val x: integer? = 123;
val y = x; // type of "y" is "integer?"
val z = y!!; // type of "z" is "integer"
val p = require(y); // type of "p" is "integer"
Tuple type¶
Examples:
(integer)
- one value(integer, text)
- two values(integer, (text, boolean))
- nested tuple(x: integer, y: integer)
- named fields (can be accessed asA.x
,A.y
)
Tuple types are compatible only if names and types of fields are the same:
(x:integer, y:integer)
and(a:integer,b:integer)
are not compatible.(x:integer, y:integer)
and(integer,integer)
are not compatible.
Collection types¶
Collection types are:
list<T>
- an ordered listset<T>
- an unordered set, contains no duplicatesmap<K,V>
- a key-value map
Collection types are mutable, elements can be added or removed dynamically.
Only a non-mutable type can be used as a map
key or a set
element.
Following types are mutable:
- Collection types (
list
,set
,map
) - always. - Nullable type - only if the underlying type is mutable.
- Record type - if the record has a mutable field, or a field of a mutable type.
- Tuple - if a type of an element is mutable.
GTXValue¶
GTXValue
is a type used to repsesent 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 map.
Some Rell types are not GTX-compatible. Values of such types cannot be converted to/from GTXValue
, and the types
cannot be used as types of operation/query parameters or result.
Rules of GTX-compatibility:
- a
map<K,V>
is GTX-compatible only ifK
istext
. range
is not GTX-compatible- a complex type is not GTX-compatible if a type of its component is not GTX-compatible
For queries, a type must be pretty-GTX-compatible. Rules are:
- a type must be GTX-compatible
- for tuples: either all fields have names, or no field has a name
- component types must be pretty-GTX-compatible as well
Subtypes¶
If type B
is a subtype of type A
, a value of type B
can be
assigned to a variable of type A
(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?)
.
Module definitions¶
Class¶
class company {
name: text;
address: text;
}
class user {
firstName: text;
lastName: text;
yearOfBirth: integer;
mutable salary: integer;
}
If attribute type is not specified, it will be the same as attribute name:
class user {
name; // built-in type "name"
company; // user-defined type "company" (error if no such type)
}
Attributes may have default values:
class user {
homeCity: text = 'New York';
}
Keys and Indices¶
Classes can have key
and index
clauses:
class user {
name: text;
address: text;
key name;
index address;
}
Keys and indices may have multiple attributes:
class user {
firstName: text;
lastName: text;
key firstName, lastName;
}
Attribute definitions can be combined with key
or index
clauses,
but such definition has restrictions (e. g. cannot specify mutable
):
class user {
key firstName: text, lastName: text;
index address: text;
}
Class annotations¶
class user (log) {
name: text;
}
The log
annotation has following effects:
- Special attribute
transaction
of typetransaction
is added to the class. - When an object is created,
transaction
is set to the result ofop_context.transaction
(current transaction). - Class cannot have mutable attributes.
- Objects cannot be deleted.
Object¶
Object is similar to class, but there can be only one instance of an object:
object event_stats {
mutable event_count: integer = 0;
mutable last_event: text = 'n/a';
}
Reading object attributes:
query get_event_count() = event_stats.event_count;
Modifying an object:
operation process_event(event: text) {
update event_stats ( event_count += 1, last_event = event );
}
Features of objects:
- Like classes, objects are stored in a database.
- Objects are initialized automatically during blockchain initialization.
- Cannot create or delete an object from code.
- Attributes of an object must have default values.
Record¶
Record declaration:
record user {
name: text;
address: text;
mutable balance: integer = 0;
}
- Attributes are immutable by default, and only mutable when declared with
mutable
keyword. - An attribute may have a default value, which is used if the attribute is not specified during construction.
Creating record values:
val u = user(name = 'Bob', address = 'New York');
Same rules as for the create
expression apply: no need to specify attribute name if it can be resolved implicitly
by name or type:
val name = 'Bob';
val address = 'New York';
val u = user(name, address);
val u2 = user(address, name); // Order does not matter - same record object is created.
Record attributes can be accessed using operator .
:
print(u.name, u.address);
Safe-access operator ?.
can be used to read or modify attributes of a nullable record:
val u: user? = findUser('Bob');
u?.balance += 100; // no-op if 'u' is null
Enum¶
Enum declaration:
enum currency {
USD,
EUR,
GBP
}
Values are stored in a database as integers. Each constant has a numeric value equal to its position in the enum (the first value is 0).
Usage:
var c: currency;
c = currency.USD;
Enum-specific functions and properties:
val cs: list<currency> = currency.values() // Returns all values (in the order in which they are declared)
val eur = currency.value('EUR') // Finds enum value by name
val gbp = currency.value(2) // Finds enum value by index
val usdStr: text = currency.USD.name // Returns 'USD'
val usdValue: integer = currency.USD.value // Return 0.
Query¶
- Cannot modify the data in the database (compile-time check).
- Must return a value.
- If return type is not explicitly specified, it is implicitly deducted.
- Parameter types and return type must be pretty-GTX-compatible.
Short form:
query q(x: integer): integer = x * x;
Full form:
query q(x: integer): integer {
return x * x;
}
Operation¶
- Can modify the data in the database.
- Does not return a value.
- Parameter types must be GTX-compatible.
operation createUser(name: text) {
create user(name = name);
}
Function¶
- Can return nothing or a value.
- Can modify the data in the database when called from an operation (run-time check).
- Can be called from queries, operations or functions.
- If return type is not specified explicitly, it is
unit
(no return value).
Short form:
function f(x: integer): integer = x * x;
Full form:
function f(x: integer): integer {
return x * x;
}
When return type is not specified, it is considered unit
:
function f(x: integer) {
print(x);
}
Expressions¶
Values¶
Simple values:
- Null:
null
(type isnull
) - Boolean:
true
,false
- Integer:
123
,0
,-456
- Text:
'Hello'
,"World"
- Byte array:
x'1234'
,x"ABCD"
Text literals may have escape-sequences:
- Standard:
\r
,\n
,\t
,\b
. - Special characters:
\"
,\'
,\\
. - Unicode:
\u003A
.
Tuple:
(1, 2, 3)
- three values(123, 'Hello')
- two values(456,)
- one value (because of the comma)(789)
- not a tuple (no comma)(a = 123, b = 'Hello')
- tuple with named fields
List:
[ 1, 2, 3, 4, 5 ]
Map:
[ 'Bob' : 123, 'Alice' : 456 ]
Operators¶
Special:¶
.
- member access:user.name
,s.sub(5, 10)
()
- function call:print('Hello')
,value.str()
[]
- element access:values[i]
Null handling:¶
?:
- Elvis operator:x ?: y
returnsx
ifx
is notnull
, otherwise returnsy
.?.
- safe access operator:x?.y
returnsx.y
ifx
is notnull
, otherwise returnsnull
; similarly,x?.y()
returns eitherx.y()
ornull
.!!
- null check:x!!
returnsx
ifx
is notnull
, otherwise throws an exception.
Examples:
val x: integer? = 123;
val y = x; // type of "y" 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?.hex(); // type of "q" is "text?"
Comparison:¶
==
!=
===
!==
<
>
<=
>=
Operators ==
and !=
compare values. For complex types (collections, tuples, records) they compare member
values, recursively. For class
object values only object IDs are compared.
Operators ===
and !==
compare references, not values. They can be used only on types:
tuple, record
, list
, set
, map
, GTXValue
, range
.
Example:
val x = [1, 2, 3];
val y = list(x);
print(x == y); // true - values are equal
print(x === y); // false - two different objects
Arithmetical:¶
+
-
*
/
%
Logical:¶
and
or
not
Other:¶
in
- check if an element is in a range/set/map
Statements¶
Local variable declaration¶
Constants:
val x = 123;
val y: text = 'Hello';
Variables:
var x: integer;
var y = 123;
var z: text = 'Hello';
Basic statements¶
Assignment:
x = 123;
values[i] = z;
y += 15;
Function call:
print('Hello');
Return:
return;
return 123;
Block:
{
val x = calc();
print(x);
}
If statement¶
if (x == 5) print('Hello');
if (y == 10) {
print('Hello');
} else {
print('Bye');
}
if (x == 0) {
return 'Zero';
} else if (x == 1) {
return 'One';
} else {
return 'Many';
}
Loop statements¶
For:
for (x in range(10)) {
print(x);
}
for (u in user @* {}) {
print(u.name);
}
The expression after in
may return a range
or a collection
(list
, set
, map
).
While:
while (x < 10) {
print(x);
x = x + 1;
}
Break:
for (u in user @* {}) {
if (u.company == 'Facebook') {
print(u.name);
break;
}
}
while (x < 5) {
if (values[x] == 3) break;
x = x + 1;
}