Skip to main content
Version: 1.9.2

Functions

LIGO functions are the basic building blocks of contracts. Contracts can have any number of functions.

Entrypoints are a special case of functions. Outside consumers can call only the functions that are designated as entrypoints. Entrypoints must also follow certain rules that do not apply to functions in general. See Entrypoints.

As in JavaScript, you can declare functions with the function keyword or the const or let keywords.

Here are some examples:

function add_function (x: int, y: int): int {
const z = x + y;
return z;
}
const add_function_const = (x: int, y: int): int => {
const z = x + y;
return z;
}

This type of function must use the return keyword to set the return value. If it does not use return, the function returns unit by default.

Also as in JavaScript, when you declare functions with the const or let keywords, you can replace the body of the function with a single expression and omit the return keyword. The add_expression function in the following example is equivalent to the add_function and add_function_const functions in the previous example:

const add_expression = (x, y) => x + y; // Single-expression body

To call a function, use its name and pass the correct number and type of parameters, as in this example:

const five_plus_three = add_expression(5, 3);

By default, LIGO warns about unused parameters inside functions. If the function does not use one of its parameters, you can prefix its name with an underscore to hide the warning, as in this example:

const drop = (x, _y) => x; // _y silently ignored

Polymorphic functions

Polymorphic functions can take advantage of parametric types to accept a wide variety of inputs instead of only a single input type. For more information, see Polymorphic functions.

Scope and side effects

Functions can access, or capture, immutable variables and functions that are outside their code as long as those variables and functions are in their scope. For example, a function can capture another function in its scope so it can call it, as in this example:

const double_incr = x => add_expression (x, x) + 1; // add_expression is captured from outside

Capturing also works with nested functions just like two functions at the same scope level:

function convoluted_doubling (x) {
const addX = y => x + y; // x is bound by convoluted_doubling
return addX(x);
}

Functions cannot change values outside their scope, including values passed as parameters. In this example, a function tries to change a variable and a parameter but fails:

let myConstant = 5;
const addConstant = (x) => {
myConstant++; // Mutable variable "myConstant" not found.
x += myConstant; // Mutable variable "x" not found.
return x + myConstant;
}

Currying functions

To simplify a function for use in different contexts, you can create new functions that decompose the original function into simpler functions that take fewer parameters. Creating these simplified functions is called currying, and it enables partial application.

For example, this code creates a function that accepts a single parameter, a tuple that contains two integers. Then it creates a curried function that accepts two separate integers as parameters and passes them to the first function as a tuple. Then it uses partial application to create a function that accepts one parameter and passes it to the curried function:

const add = ([x, y]) => x + y; // Uncurried
const add_curry = (x, y) => add([x, y]); // Curried
const increment = add_curry(1); // Partial application
const one = increment(0);

Function expressions

Sometimes you need to use a function only once, so it doesn't need a name. Also, giving the function a name might incur a slight risk of cluttering the scope or being captured unintentionally. Functions without names are called anonymous functions, function expressions, or lambdas.

Function expressions are called arrow functions in JsLIGO:

const sum = (x,y) => x + y;
const increment = x => x + 1; // Or (x) => x + 1

Note that when there is a single parameter that is not given a type, the parentheses are not necessary, but they are if the return type is given, as in this example:

const decrement = (x) : int => x - 1;

The anonymous function (x,y) => x + y is an expression, and you can use it without a name in contexts where functions of type (x: int, y: int) => int are valid.

Note: When a function takes one argument that is a tuple, parentheses are mandatory, as in this example:

const comp_sum = ([x,y]) => x + y;

That function is different from the function (x,y) => x + y, which takes two arguments. In other words, sum has type (x: int, y: int) => int and comp_sum has type ([x,y] : [int,int]) => int.

Recursion

Recursive functions are defined and called using the same syntax as non-recursive functions.

function sum (n: int, acc: int) : int {
if (n < 1) return acc else return sum (n-1, acc + n);
};
function fibonacci (n: int, n_1: int, n_0: int): int {
if (n < 2) return n_1 else return fibonacci (n-1, n_1 + n_0, n_1);
};

This means that all values, including functions, declared in the same block or top-level scope, must have different names, because they can all potentially be mutually recursive.

Higher-order functions

Functions that take a function as a parameter or return a function are known as higher-order functions. This example accepts two functions and composes them into one, as in mathematics:

const compose = f => g => x => f (g (x));
const double_incr = compose (x => x + 1) (x => 2*x) // 2*x + 1

You can also pass named functions as arguments:

const increment = x => x + 1;
const double = x => 2*x;
const double_incr2 = compose (increment) (double);

Inlining

Inlining is the process of embedding the code of a function instead of storing the function as a separate block of code. To save space, the LIGO compiler inlines function code rather than using a separate function when the function is used only once and is pure, which means it has no side effects. In this context, side effects include creating operations or exceptions.

The compiler does not inline functions that are used more than once or are not pure because doing so often results in larger contracts. However, in some cases, you may want to force the compiler to inline function code. You can force the compiler to inline functions that are used more than once, but you cannot force the compiler to inline functions that cause side effects.

To force inlining, use the @inline decorator.

@inline
const fst = (p: [nat, nat]) => p[0];
@entry
const main = (p: [nat, nat], s: [nat, nat]) : [list<operation>, [nat, nat]] =>
[[], [fst([p[0], p[1]]), fst([s[1], s[0]])]];

You can measure the difference between inlining and not inlining with the info measure-contract command. In this case, inlining the function saves space because the function is so small. This table shows the results of the command ligo info measure-contract inline.jsligo with and without the @inline decorator:

With inlining46 bytes
Without inlining97 bytes
info

Note that these results can change due to ongoing work to optimise output of the LIGO compiler.