A function is a group of statements that together perform a task. Every program has at least one function, which is main(), and all the most trivial programs can define additional functions.
This is an example of how to declare a function with the name function
:
function :: (arg1: int, arg2: int, arg3: int) -> int {
// write function code here.
}
Here is how you call the function:
function(1, 2, 3);
function(arg1=1, arg2=2, arg3=3);
Functions can take multiple arguments and return multiple values. Unlike languages such as Rust or Go, functions do not return tuple object values, but rather return the values in registers.
function :: (arg1: int, arg2: int, arg3: int) -> ret1: int, ret2: int {
// write function code here.
}
You can ignore some or all of the return values of a function with multiple return values, unless the return value is tagged with a #must
directive. You can only get the return values in the order that you return the values, meaning in order to get the second returned value, you need to get the first value.
function :: () -> int, int, int {
}
a := function(); // get only the first value in the function
a, b := function(); // get the first and second value in the function
a, b, c := function(); // get all the return values.
Named and default return values
A named return value is merely a comment for a programmer. The name of the return value is not a variable, and is NOT a variable declaration. In the example below, the -> a: int, b: int {
part of the function signature does not declare a variable. As one can see, you need to declare a: int = 100;
and b: int = 200;
later on in the function body.
function :: () -> a: int, b: int {
a: int = 100;
b: int = 200;
return a, b;
}
Jai functions can have default return values. A default return value, similar to a default function argument, is a value provided in a function that is automatically assigned by the compiler if the caller of the function doesn’t provide a value.
function :: (var: bool) -> a: int = 100, b: int = 200 {
if var == true then
return; // 100, 200 are automatically returned by default
else
return 1_000_000; //
}
a, b := function(true);
print("(%, %)\n", a, b); // prints out '(100, 200)'
a, b := function(false);
print("(%, %)\n", a, b); // prints out '(1000000, 200)'
Function Overloading
Functions can be overloaded with several definitions for the same function name. The functions must differ from each other by the types and/or the number of arguments passed into it.
function :: (x: int) {
print("function overload 1\n"); // first overloaded function
}
function :: (x: int, y: int) {
print("function overload 2\n"); // second overloaded function
}
function(1); // prints "function overload 1"
function(1, 2); // prints "function overload 2"
In this example, the first overloaded function is called, printing out “function overload 1”, then the second overloaded function is called, printing out “function overload 2”.
#must
directive
You can tag a return value with a #must
directive to require the caller of the function to receive the return values of the function.
funct :: (x: int) -> int #must {
}
Default Arguments
Just like C++, Jai functions can have default arguments. A default argument is a value provided in a function that is automatically assigned by the compiler if the caller of the function doesn’t provide a value.
// a = 0 by default
funct :: (a: int = 0) {
}
f(); // a is passed 0
If a default parameter is used, parameters following a
need to be explicitly passed to a
.
funct :: (a: int = 0, b: int, c: int) {
}
funct(b=8, c=0);
Varargs Function
A function can take variadic number of arguments, or a variable number of arguments into a function. Consider the print
function inside the Basic module. Notice that it can take in either 1 argument, 2 arguments, or 3 arguments:
#import "Basic";
x, y, z, w := 0, 1, 2, 3, 4;
print("Hello!\n"); //
print("x=%\n", x);
print("x=%, y=%, z=%, w=%\n", x, y, z, w);
You can create your own variadic function using the following syntax:
#import "Basic";
var_args :: (args: ..int) {
print("args=%\n", args);
}
var_args(1,2,3,4,5,6,7);
args := int.[1,2,3,4,5,6,7];
var_args(..args); // same as doing var_args(1,2,3,4,5,6,7);
var_args(args=args); // same as doing var_args(1,2,3,4,5,6,7);
In variadic functions, the variadic arguments are passed to the function as an array, and arrays can be passed to variadic functions by adding a ..
to the array identifier.
Inner Functions
Functions can be defined inside the scope of other functions. The function defined inside another function cannot access the local variables of the outer function.
function :: () {
x := 1;
inner_function();
inner_function();
inner_function :: () {
print("This is an inner function\n");
// x = 42; // this does not work! cannot access variable of inner_function scope!
}
}
If you want, however, to define “inner functions” that do access the outer scope variables, than one way of doing this is to use #expand
, see also the section below:
function :: () {
inner_function :: () #expand {
`x = 42;
}
x := 1;
inner_function();
}
Inlined Functions
Functions can be inlined through adding inline
to the function declaration. inline
replaces the function call with the actual body of the function. Unlike C or C++, inline
in Jai is not just a suggestion to inline a function, but forces the compiler to attempt to inline a function.
A function can be inlined from the function definition as follows:
function :: inline (a: int, b: int)->int {
//... function body
}
A function can also be inlined from the place the function is called:
answer := inline function(10, 20);
Lambda Expressions
Lambda Expressions, i.e. simple small one-line functions, can be declared as follows:
funct :: (a, b) => a + b;
The above lambda expression takes in two parameters a
and b
, adds them together, and returns a+b
.
Let’s create an anonymous lambda expression to map a bunch of values from array_a
to array_b
. In this example, we add 100 to all the values in array_a
, and place those values in array_b
.
map :: (array_a: [$N] $T, f: (T)->T)-> [N]T {
array_b: [N] T;
for i: 0..N-1 {
array_b[i] = f(array_a[i]);
}
return array_b;
}
array_a := int.[1, 2, 3, 4, 5, 6, 7, 8];
// array_b := int.[101, 102, 103, 104, 105, 106, 107, 108];
array_b := map(array_a, x=>x+100);
Unlike C++ or Rust, closures and capture blocks are not supported! The best way to get the desired functionality of closures would be to write a macro.