Function
In Rell, functions streamline repetitive tasks by encapsulating reusable logic, such as validation or data retrieval.
- Functions can return various data types, including custom entities and objects. Without an explicit return type
declaration, a function implicitly returns
unit
(no value). - When called within operations, functions can modify the database, enabling dynamic and interactive applications.
- Functions can be invoked from queries, operations, and other functions, promoting code organization and modularity.
Short form (concise expressions):
function f(x: integer): integer = x * x;
Full form (multi-line logic):
function f(x: integer): integer {
return x * x;
}
Return types:
- Explicitly declared: Use a colon and the desired type name to clearly define the expected return value.
- Implicit
unit
: If no return type is specified, the function implicitly returnsunit
.
function f(x: integer) {
print(x);
}
Default parameter values
Streamline function calls by providing default values for certain parameters. The default parameters get used if you don't specify the parameters in the function call.
function f(user: text = 'Bob', score: integer = 123) {...}
...
f(); // means f('Bob', 123)
f('Alice'); // means f('Alice', 123)
f(score=456); // means f('Bob', 456)
Named function arguments
Improve readability and maintainability by explicitly naming arguments during function calls.
function f(x: integer, y: text) {}
...
f(x = 123, y = 'Hello');
Using a function as a value
Pass functions as arguments to other functions, enabling powerful abstractions and code reuse. If you want to pass a function to another function, then you can use the function as a value by using the following syntax:
() -> boolean
(integer) -> text
(byte_array, decimal) -> integer
Within the parentheses, you specify the function input type of the passed function after the arrow follows the function's return type.
An example could look like this:
function filter(values: list<integer>, predicate: (integer) -> boolean): list<integer> {
return values @* { predicate($) };
}
Partial function application
Create new functions by pre-filling some arguments of an existing function, promoting code flexibility. You can use the
wildcard symbol *
to create a reference to a function (to obtain a value of a function).
function multiply(x: integer, y: integer) = x * y;
val double = multiply(2, *);
// Type of "double" is (integer) -> integer
double(456); // Invocation of multiply(2, 456) via "g"
Extendable functions
You can declare a function as extendable by adding @extendable
before the function declaration. You can define an
arbitrary number of extensions for an extendable function by expressing @extend
before the function declaration.
In the example below, function f
is a base function, and functions g
and h
are extension functions.
@extendable function f(x: integer) {
print('f', x);
}
@extend(f) function g(x: integer) {
print('g', x);
}
@extend(f) function h(x: integer) {
print('h', x);
}
When you call the base function, all its extension functions get executed, and the base function itself gets executed. However, extendable functions support a limited set of return types, and this behavior depends on the return type.
Return type-specific behavior
- Unit: All extensions and the base function execute unconditionally.
- Boolean: Extensions execute until one returns
true
; the base function executes only if all extensions returnfalse
. The last executed function's result is returned. - Optional (
T?
): Similar to Boolean, but extensions execute until one returns a non-null value. - List (
list<T>
): All extensions and the base function execute, and their lists are concatenated in the returned result. - Map (
map<K, V>
): Similar to List, but maps are combined into a union, failing if key conflicts arise.