$f = ($x) => {
$a = var['a'];
$x * $a
};
// one-line syntax for functions that have only one expression
$sq = $x => $x^2;
$res = $f(2);
$nine = $sq(3);
Functions are local variables and the same rules apply to them as to local variables.
The return value is the value of the last expression or a return statement.
A function sees all other local vars and functions declared before it. Because of this, recursion is impossible.
Variables declared before the function cannot be shadowed by its arguments or other local variables declared within the function.
Complexity of a function is the sum of complexities of all operations within it. Every time a function is called, its complexity is added to the total complexity of the AA. If a function is declared but never called, its complexity doesn't affect the complexity of the AA.
Getters are read-only functions available to other AAs and non-AA code.
They are meant to extract information about an AA state that is not directly available through state vars. E.g. in order to fetch this information one needs to perform some calculation over state vars or access several state vars and have good knowledge about the way information is stored in the AA.
Examples:
- oswap could expose functions that would calculate the price of a future exchange. Otherwise, clients would have to do non-trivial math themselves.
- the token registry could expose a single getter $getTokenDescription($asset) for reading a token description, and a similar one for decimals. Otherwise, one has to read first
$desc_hash = var['current_desc_' || $asset]
, thenvar['description_' || $desc_hash]
.
Getters are declared in a top-level getters
section which is evaluated before everything else.
['autonomous agent', {
getters: `{
$sq = $x => $x^2;
$g = ($x, $y) => $x + 2*$y;
$h = ($x, $y) => $x||$y;
$r = ($acc, $x) => $x + $acc;
}`,
init: `{
// uncomment if the AA serves as library only
// bounce("library only");
...
}`,
...
}]
The code in getters
section can contain only function declarations and constants. Request-specific information such as trigger.data
, trigger.outputs
, etc is not available in getters.
In the AA which declares them, getters can be accessed like normal functions.
Other AAs can call them by specifying the remote AA address before the function name using this syntax:
$nine = MXMEKGN37H5QO2AWHT7XRG6LHJVVTAWU.$sq(3);
or
$remote_aa = "MXMEKGN37H5QO2AWHT7XRG6LHJVVTAWU";
$nine = $remote_aa.$sq(3);
where $remote_aa
variable must be a constant (it must be known at deploy time in order calculate the complexity).
The complexity of a remote call is the complexity of its function, plus one.
All functions declared in the getters
section are publicly available. If some of them are not meant for public use, one can indicate this by a naming convention, e.g. by starting their names with an underscore $_callPrivateFunction()
but information hiding cannot be really enforced since all getters operate on public data anyway.
Getters can also be conveniently called from non-AAs. In node.js code:
const { executeGetter } = require('ocore/formula/evaluation.js');
const db = require('ocore/db.js');
const args = ["arg1", "arg2"];
const res = await executeGetter(db, aa_address, getter, args);
For remote clients, there is a light/execute_getter
command in websocket API, hopefully it will be shortly available through obyte.js.
Objects and arrays can be initialized using familiar syntax:
$obj = {a: 3, b: 7 };
$arr = [7, 2, 's', {a: 6}];
Although all local variables are single-assignment, objects and arrays can be mutated by modifying, adding, or deleting their fields:
$obj = {a: 3, b: 7 };
$obj.a = 4; // modifies an existing field
$obj.c = 10; // adds a new field
delete($obj, 'b'); // deletes a field
freeze($obj); // prohibits further mutations of the object
$arr = [7, 2, 's', {a: 6}];
$arr[0] = 8; // modifies an existing element
$arr[] = 5; // adds a new element
delete($arr, 1); // removes element 1
freeze($arr); // prohibits further mutations of the array
The left-hand selectors can be arbitrarily long .a.b[2].c.3.d[].e
. If some elements do not exist, an empty object or array is created automatically. []
adds to the end of an array. Array indexes cannot be skipped, i.e. if an array has length 5, you cannot assign to element 7. Once you are dome mutating an object, can call freeze()
to prevent further accidental mutations.
$hash = sha256($obj);
$definition = ['autonomous agent', {
...
}];
$address = chash160($definition);
sha256
now works on objects too and returns sha256 of the object's json.
chash160
is a new function that returns a 160-bit checksummed hash of an object, it is most useful for calculating an address when you know its definition, e.g. if you programmatically define a new AA and want to know its address in order to immediately trigger it. Previously, it was possible only by using various tricks e.g. a forwarder AA.
$ar = [2, 5, 9];
$ar2 = map($ar, 3, $x => $x^2);
A function is executed over each element of an array or object. The callback function can be anonymous like in the example above, or referenced by name:
$f = $x => $x^2;
$ar = [2, 5, 9];
$ar2 = map($ar, 3, $f);
It can also be a remote getter:
$ar2 = map($ar, 3, $remote_aa.$f);
The function for map, foreach, and filter accepts 1 or 2 arguments. If it accepts 1 argument, the value of each element is passed to it. If it accepts 2 arguments, key and value for objects or index and elememt for arrays are passed.
The second argument is the maximum number of elements that an array or object can have. If it is larger, the script fails. This number must be a constant so that it can be known at deploy time, and the complexity of the entire operation is the complexity of the callback function times maximum number of elements. If the function has 0 complexity, the total complexity of map/reduce/foreach/filter is assumed to be 1 independently of the max number of elements. Max number of elements cannot exceed 100.
reduce
has one additional argument for initial value:
$c = 3;
$ar = [2, 5, 9];
$acc = reduce($ar, $c, ($acc, $x) => $acc + $x, 0); // sums all elements, will return 16
The callback function for reduce
accepts 2 or 3 arguments: accumulator and value or accumulator, key, and value (accumulator, index, and element for arrays).
These functions are similar to their javascript counterparts but unlike javascript they can also operate on objects, not just arrays.
Concat operator || can now be used for arrays and objects too:
[4, 6] || [3, 1] // [4, 6, 3, 1]
{x: 1, y: 7} || {y: 8, a:9} // {x: 1, y: 8, a:9}
If the same key is found in both objects, the value from the right-hand one prevails.
Trying to concat an array and object results in error.
If either operand is a scalar, both are converted to strings and concatenated as strings. Objects/arrays become "true".
split("let-there-be-light", "-") // ["let", "there", "be", "light"]
join(["let", "there", "be", "light"], "-") // "let-there-be-light"
split("let-there-be-light", "-", 2) // ["let", "there"]
The functions are similar to their conterparts in other languages. join
can be applied to objects too, in this case the elements are sorted by key and their values are joined.
reverse([4, 8, 3]) // [3, 8, 4]
The function reverses an array and returns a new one. Deep copies of all elements are created.
Passing a non-array to this function results in error.
length([3, 7, 9]) // 3
This function now operates on objects and arrays too. When passed an object or array, it returns the number of elements in object or array. Scalar types are converted to strings as before and the length of the string is returned.
keys({b: 3, a: 8}) // ['a', 'b']
Returns the keys of an object. The keys are sorted. Passing anything but an object results in error.
replace(str, search_str, replacement)
Replaces all occurences of search_str
in str
with replacement
and returns the new string.
has_only(str, allowed_chars)
Returns true
if str
consists only of characters in allowed_chars
. allowed_chars
is a group of characters recognized by regular expressions, examples: a-z0-9
, \w
. has_only
adds +1 to complexity.
var['s'] = {a:8, b:2};
var['s'] ||= {c:6}; // concat an object
Internally, objects are stored as json strings and their length is limited. Don't try to store a structure in a state var if this structure can grow indefinitely.