Functions
Introduction
Functions can be defined at top-level (for full file scope) or within protocols (for protocol scope). One cannot define functions within a function, permission or obligation. However, invocation of functions as part of expressions in their body is possible.
Functions enable the re-use of code.
Declaration
A function is declared with the keyword function
, followed by the function name, optional function parameters, an
optional return type, a right-arrow (->
), and the body of the function.
If no return type is provided, the return type of the function will be determined by the type of its body's last
statement. It is possible to specify returns <type>
before the ->
notation. In this case, the returns <type>
will
take precedence over the automatic type resolution.
Function parameters must always be annotated by their types, which follow the usual : T
syntax. Multiple parameters
may be separated using commas. The curly braces and the return statement can be omitted if the body contains a single
expression.
Usage of right-arrow ->
notation.
function withRightArrow(param: Text, anotherParam: Number) -> {
// Important logic.
return true;
};
Usage of right-arrow ->
notation with returns.
function rightArrowReturns(param: Text, anotherParam: Number) returns Boolean -> {
// Important logic.
return true;
};
Function parameters can be of any type, except the Party
type.
function someFunction(param: Text, anotherParam: Number) returns Boolean -> {
// Important logic.
return true;
};
The return type may be omitted for functions with only side effects (and therefore return Unit).
function sideEffectFunction(otherProtocol: OtherProtocol) -> {
otherProtocol.modify['p']();
};
When the body only returns a single expression, the curly braces and return statement can be omitted.
The return type is inferred:
function expressionSyntaxInfer(x: Number) -> x == 1;
The return type is explicitly specified:
function expressionSyntax(x: Number) returns Boolean -> x == 1;
Call
The syntax for a function call is its name followed by named or unnamed function parameter expressions between round brackets. Mixing named and unnamed parameters is not supported.
Given
function equals(par1: Text, par2: Number) returns Boolean -> {
return par1 == par2.toText();
};
the following function calls are valid:
equals("23", 12)
equals(par1 = "23", par2 = 12)
equals(par2 = 12, par1 = "23")
Guards
Deprecated syntax (since 2024.1.1, to be removed in 2025.1.0)
The guard
keyword has been deprecated: use require
instead (see below)
Require statements
A function may be qualified using one or more require
conditions and may be placed anywhere inside its block body.
Each require
consists of a boolean expression and a message. The boolean expression must evaluate to true for the
condition to pass. If the expression were to evaluate to false, the check would fail and the specified message would be
returned. A message must be unique across all the require
statements defined within the same function, and it should
provide a useful user-facing information.
A function with a require
may be called any number of times anywhere in the body of a Protocol init block, a Protocol
action, or another function.
function myRequirements(x: Number) returns Unit -> {
require(x < 5, "x must be less than 5");
require(x > 0, "x must be greater than 0");
};
function myOtherRequire(x: Number) returns Unit -> {
require(x > 3, "x must be greater than 0");
myRequirements(x);
};
protocol[a] MyProtocolInitWithRequirements(var x: Number) {
init {
myRequirements(x);
}
};
protocol[a] MyProtocolPermissionWithRequirements(var x: Number) {
permission[a] myPermission(y: Number) {
myRequirements(y + x);
};
};
For example, you could use it as part of an if-else expression, as part of a match expression, or in a for loop:
function myOtherRequirements(x: Number) returns Unit -> {
// if-else
if (x > 1) myRequirements (x) else myOtherRequire (x + 1);
// match
var myOpt = optionalOf(10);
match (myOpt) {
is Some<Number> -> myRequirements(myOpt.result)
};
for (i in listOf(1, 2, 3)) {
myRequirements(i);
};
};
Anonymous (lambda) functions
Anonymous functions (lambdas) are functions without a name. They use the same syntax as regular functions, but the function name is omitted.
var lambda = function(element: ElementType) returns ReturnType -> expression;
var lambda = function(element: ElementType) -> expression;
var lambda = function(element: ElementType) -> { return 10; };
Anonymous functions can be used with built-in functions, such as List.map()
. Furthermore, they can be assigned to
Functional variables.
listOf(1, 2, 3, 4).map(function(x: Number) returns Number -> x * 10);
Closures
When an anonymous function is constructed, it automatically copies the values of all variables that are referenced in the anonymous function body but absent in its arguments. The anonymous function together with the captured environment is referred to as a closure.
function closure() returns Number -> {
var x = 1;
var z = function() returns Number -> x;
return z();
};
which returns
1
The fact that the value is passed by value (and not by reference) means that any changes to the variable value are not reflected within the anonymous function.
function anotherClosure() returns Number -> {
var x = 1;
var z = function() returns Number -> x;
x = 3;
return z();
};
which (also) returns
1
Note that although they are also passed by value, protocol variables are references themselves. Therefore, changes to the protocol referred to by a variable are observed in the anonymous function as well:
protocol [p] Proto(var x: Number) {
permission[p] setX(y: Number) { this.x = y; }
}
function protocolClosure() returns Number -> {
var p = Proto['a'](1);
var z = function() returns Number -> p.x;
p.setX['a'](2);
return z();
};
which returns
2
This of course does not apply to the reference itself -- if the variable p
is reassigned, the anonymous function still
refers to the original protocol.
function anotherProtocolClosure() returns Number -> {
var p = Proto['a'](1);
var z = function() returns Number -> p.x;
p = Proto['a'](2);
return z();
};
which returns
1
The reference to this
is captured the same way. Changes to this protocol are also observed in the anonymous function,
even if this
keyword is not used explicitly:
protocol [p] AnotherProto(var x: Number) {
permission [p] explicitThis() returns Number {
x = 0;
var a = function() returns Number -> this.x;
x = 1;
return a();
}
permission [p] implicitThis() returns Number {
x = 0;
var b = function() returns Number -> x;
x = 1;
return b();
}
}
both of which return
1
Calling methods and actions on any captured variable is valid:
protocol [p] A() {
var a: Number = 1;
permission [p] b() {};
}
protocol [p] B(var g: A) {
var a: Number = 3;
function b() -> {}
permission [p] c() {}
permission [p] d(e: Number) returns () -> Unit {
var f = 1;
return function() returns Unit -> {
// OK, modifying field
this.a = 4;
// OK, modifying field without explicit this
a = 5;
// OK, calling a captured method
this.b();
// OK, calling a captured method without explicit this
b();
// OK, calling an action on self
this.c[p]();
// OK, calling an action on another protocol
g.b[p]();
};
}
}
Variables captured by an anonymous function are immutable by default, and any attempt to modify them will result in a compile error:
protocol [p] A() {
var a = 1;
}
protocol [p] B(var g: A) {
permission [p] d(e: Number) returns () -> Unit {
var f = 1;
return function() returns Unit -> {
// ERROR, assignment to a captured parameter
e = 1;
// ERROR, assignment to a captured local
f = 1;
// ERROR, assignment to a field of another protocol
g.a = 1;
};
}
}