Skip to main content
Version: 0.62.0

Functions

LIGO functions are the basic building block of contracts. For example, entrypoints are functions and each smart contract needs a main function that dispatches control to the entrypoints (it is not already the default entrypoint).

The semantics of function calls in LIGO is that of a copy of the arguments but also of the environment. In the case of JsLIGO, this means that any mutation (assignment) on variables outside the scope of the function will be lost when the function returns, just as the mutations inside the functions will be.

Declaring Functions

Functions in JsLIGO are defined using the let or const keyword, like other values. The difference is that parameters are provided after the value name, with its type, then followed by the return type.

Here is how you define a basic function that sums two integers:

let add = (a: int, b: int) => a + b;

You can call the function add defined above using the LIGO compiler like this:

ligo run evaluate-call gitlab-pages/docs/language-basics/src/functions/blockless.jsligo '(1,2)' --entry-point add
# Outputs: 3

As in CameLIGO, the function body is a single expression, whose value is returned. If the body contains more than a single expression, you use block between braces:

let myFun = (x: int, y: int) => {
let doubleX = x + x;
let doubleY = y + y;
return doubleX + doubleY;
};

Note that JsLIGO, like JavaScript, requires the return keyword to indicate what is being returned. If return is not used, it will be the same as return unit.

By default, LIGO will warn about unused arguments inside functions. In case we do not use an argument, we can use the wildcard _ to prevent warnings. Either use _ instead of the argument identifier:

let k = (x: int, _: int) => x;

or use an identifier starting with wildcard:

let k_other = (x: int, _y: int) => x;

Anonymous functions (a.k.a. lambdas)

It is possible to define functions without assigning them a name. They are useful when you want to pass them as arguments, or assign them to a key in a record or a map.

Here is how to define an anonymous function:

let increment = b => (a => a + 1) (b);
let a = increment(1); // a == 2

You can check the value of a defined above using the LIGO compiler like this:

ligo run evaluate-expr gitlab-pages/docs/language-basics/src/functions/anon.jsligo --entry-point a
# Outputs: 2

If the example above seems contrived, here is a more common design pattern for lambdas: to be used as parameters to functions. Consider the use case of having a list of integers and mapping the increment function to all its elements.

let incr_map = l =>
List.map(i => i + 1, l);

You can call the function incr_map defined above using the LIGO compiler like so:

ligo run evaluate-call
gitlab-pages/docs/language-basics/src/functions/incr_map.jsligo --entry-point incr_map
"list([1,2,3])"
# Outputs: CONS(2 , CONS(3 , CONS(4 , LIST_EMPTY()))), equivalent to list([ 2 , 3 , 4 ])

Nested functions (also known as closures)

It is possible to define a functions inside another function. These functions have access to variables in the same scope.

let closure_example = i => {
let closure = j => i + j;
return closure(i);
};

Recursive functions

LIGO functions are not recursive by default, the user need to specify that the function is recursive.

At the moment, recursive function are limited to one, e.g., a tuple, parameter and recursion is limited to tail recursion, that is, the recursive call should be the last expression of the function.

let sum = (n: int, acc: int): int => {
if (n < 1) {
return acc;
} else {
return sum (n-1, acc + n);
};
};
let fibo = (n: int, n_1: int, n_0: int): int => {
if (n < 2) {
return n_1;
} else {
return fibo (n-1, n_1 + n_0, n_1);
};
};