Functions¶
Functions in LSP are first-class citizen. Thus, you can manipulate functions as any other value. They can be passed to other functions as arguments, assigned to variables, ... You can even create functions dynamically with lambda expressions. This property has one main consequence: functions use the same namespace than global or local variables. There are two kinds of functions:
- Built-in functions that are provided by the modeler
(such as
io.println
,io.openRead
ormap.count
for example) - User-defined functions. A LSP program is a sequence of user-defined functions. No statement or expression can be written outside of functions.
Function definition¶
A user function is declared using the function keyword followed by its name. The parameters are given in parenthesis separated by a comma, and its code is encapsulated between braces {}. There is no limitation on the number of parameters. Parentheses are mandatory even when no parameter is given.
function
: 'function' identifier '(' func_identifier_list ')' block_statement
| 'function' identifier '(' ')' block_statement
;
func_identifier_list
: identifier
| func_identifier_list ',' identifier
;
The function definition does not execute the function statements. They are executed only when the function is called. Furthermore, and contrary to languages like C or C++, you don’t have to declare your function before using it in your code. Thus, the following code is valid:
function a() {
// You can write b(), even if b is not known by the parser at this step.
// But b must be a function when executing function a.
b();
}
function b() {
...
}
Function parameters are defined as local variables. As a consequence, any local variable with the same name as a function argument is masked by the function argument.
Arguments are passed by object reference. It means that you can’t modify the variables used to push the argument of the function in the calling code, but if a mutable object is passed (like a map or a file), the caller will see any change the callee makes to it.
For example:
function f(a) {
a[2] = 8; // applies to the original map
a = {2,3,4}; // no impact on the map since it just assigns locally a new map to the name "a"
}
function g() {
a[0] = 0;
f(a);
print(a); // will print [ 0 => 0 2 => 8 ]
}
Function body defines a new scope for local variables. In fact, user-defined function is the widest possible scope for a local variable. A local variable cannot exist outside a function.
Return statement¶
Values are returned by using the optional return statement. Any value of any type can be returned (including functions that are first class citizen). This causes the function to end its execution and pass the control back to the line from which it was called. Values are returned by object reference.
If the code of a function completes its execution without encountering a return statement, the nil value is returned by default. Consequently all functions return a (possibly nil) value. The returned type is not necessarily the same in all branches: for example the following code is valid:
function f(a) {
if (a) return 2;
else return "zz";
}
Function call¶
To invoke a function, simply write the name of the function followed by its arguments. Parameters are evaluated in the order of their declaration. There is no need to declare the function before using it:
f(a, b, c); // invokes f with parameters a, b, c
The number of arguments passed to the function must match the number of arguments used to declare the function. Thus, the following code will throw an exception at runtime:
// This function accepts exactly 3 arguments
function f(a, b, c) {
...
}
function g() {
// This statement will throw an error: only 2 arguments passed to f
f(12, -12);
}
This constraint does not apply to functions declared as variadic. In that case,
the function does not have a fixed number of parameters. For instance the
function println
allows a variable number of parameters and the following
code is valid:
println();
println("abc");
println("abc", "de");
println("abc", "de", 180);
For now, only builtin-functions can be variadic. Variadic functions are particularly useful with the operators of the solver (sum, min, max). To see how to use variadic functions with an unknown number of arguments or how to call variadic functions with arguments that are in a list, see the next section Variadic function call.
Variadic function call¶
The iteration format introduced in section For statements can be used to specify the parameters of functions. This syntax cannot be used with user-defined functions. It is only allowed for built-in functions.
For instance: println[i in 1..4](i, " ")
is equivalent to
println(1, " ", 2, " ", 3, " ",4," ")
. Similarly to the for statement,
these iterations can be nested and filtered:
// Display even numbers first, then odd numbers
println[k in 0..1][i in 1..10 : i % 2 == k](i, " ");
This functionality is especially useful for modeling expressions or for arithmetic computations, for instance:
x <- sum[i in 1..10](w[i] * y[i]); // declare x as a sum of 10 terms
v = min[j in 1..5][k in 1..9](m[j](k]); // retrieve the smallest element of a matrix
v = prod[v in a : v != 0](a[j]); // compute the product of non-zero elements of an map a