Skip to content

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;
        };
    }
}