Functions

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.

1 Like

@danieltan1517 is there a difference between -> and =>? Or is it a typo.

=> is for lambda expressions. -> is for normal functions.