ligo/gitlab-pages/docs/language-basics/functions.md
2020-03-04 10:35:34 +01:00

8.3 KiB

id title
functions 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 PascaLIGO, 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

There are two ways in PascaLIGO to define functions: with or without a block.

Blocks

In PascaLIGO, blocks enable the sequential composition of instructions into an isolated scope. Each block needs to include at least one instruction.

block { a := a + 1 }

If we need a placeholder, we use the instruction skip which leaves the state unchanged. The rationale for skip instead of a truly empty block is that it prevents you from writing an empty block by mistake.

block { skip }

Blocks are more versatile than simply containing instructions: they can also include declarations of values, like so:

block { const a : int = 1 }

Functions in PascaLIGO are defined using the function keyword followed by their name, parameters and return type definitions.

Here is how you define a basic function that computes the sum of two integers:

function add (const a : int; const b : int) : int is
  block {
    const sum : int = a + b
  } with sum

The function body consists of two parts:

  • block { <instructions and declarations> } is the logic of the function;
  • with <value> is the value returned by the function.

Blockless functions

Functions that can contain all of their logic into a single expression can be defined without the need of a block:

function identity (const n : int) : int is block { skip } with n  // Bad! Empty block not needed!

function identity (const n : int) : int is n  // Blockless

The value of the expression is implicitly returned by the function. Another example is as follows:

function add (const a: int; const b : int) : int is a + b

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

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

Functions in CameLIGO are defined using the let keyword, like other values. The difference is that a succession of parameters is provided after the value name, followed by the return type. This follows OCaml syntax. For example:

let add (a : int) (b : int) : int = a + b

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

ligo run-function gitlab-pages/docs/language-basics/src/functions/blockless.mligo add '(1,2)'
# Outputs: 3

CameLIGO is a little different from other syntaxes when it comes to function parameters. In OCaml, functions can only take one parameter. To get functions with multiple arguments like we are used to in imperative programming languages, a technique called currying is used. Currying essentially translates a function with multiple arguments into a series of single argument functions, each returning a new function accepting the next argument until every parameter is filled. This is useful because it means that CameLIGO supports partial application.

Currying is however not the preferred way to pass function arguments in CameLIGO. While this approach is faithful to the original OCaml, it is costlier in Michelson than naive function execution accepting multiple arguments. Instead, for most functions with more than one parameter, we should gather the arguments in a tuple and pass the tuple in as a single parameter.

Here is how you define a basic function that accepts two integers and returns an integer as well:

let add (a, b : int * int) : int = a + b             // Uncurried
let add_curry (a : int) (b : int) : int = add (a, b) // Curried
let increment : int -> int = add_curry 1             // Partial application

You can run the increment function defined above using the LIGO compiler like this:

ligo run-function gitlab-pages/docs/language-basics/src/functions/curry.mligo increment 5
# Outputs: 6

The function body is a single expression, whose value is returned.

Functions in ReasonLIGO are defined using the let keyword, like other values. The difference is that a tuple of parameters is 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, b): (int, int)) : int => a + b;

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

ligo run-function gitlab-pages/docs/language-basics/src/functions/blockless.religo add '(1,2)'
# Outputs: 3

As in CameLIGO and with blockless functions in PascaLIGO, 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, y) : (int, int)) : int => {
  let doubleX = x + x;
  let doubleY = y + y;
  doubleX + doubleY
};

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:

function increment (const b : int) : int is
   (function (const a : int) : int is a + 1) (b)
const a : int = increment (1); // a = 2

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

ligo evaluate-value gitlab-pages/docs/language-basics/src/functions/anon.ligo a
# Outputs: 2
let increment (b : int) : int = (fun (a : int) -> a + 1) b
let a : int = increment 1 // a = 2

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

ligo evaluate-value gitlab-pages/docs/language-basics/src/functions/anon.mligo a
# Outputs: 2
let increment = (b : int) : int => ((a : int) : int => a + 1) (b);
let a : int = increment (1); // a == 2

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

ligo evaluate-value gitlab-pages/docs/language-basics/src/functions/anon.religo 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.

function incr_map (const l : list (int)) : list (int) is
  List.map (function (const i : int) : int is i + 1, l)

Note that list_map is deprecated.

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

ligo run-function
gitlab-pages/docs/language-basics/src/functions/incr_map.ligo incr_map
"list [1;2;3]"
# Outputs: [ 2 ; 3 ; 4 ]
let incr_map (l : int list) : int list =
  List.map (fun (i : int) -> i + 1) l

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

ligo run-function
gitlab-pages/docs/language-basics/src/functions/incr_map.mligo incr_map
"list [1;2;3]"
# Outputs: [ 2 ; 3 ; 4 ]
let incr_map = (l : list (int)) : list (int) =>
  List.map ((i : int) => i + 1, l);

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

ligo run-function
gitlab-pages/docs/language-basics/src/functions/incr_map.religo incr_map
"list [1;2;3]"
# Outputs: [ 2 ; 3 ; 4 ]