Skip to content

Language Concepts: Functions

Luiz Fernando edited this page Feb 28, 2015 · 6 revisions

Functions

Statements cannot be executed in the top-level of the file (in previous examples containing what appear to be statements in the top-level are actually implied to be inside a function, and so are the next examples); in order to execute statements they must be contained within functions.

Functions in ZScript are defined with func keyword, followed by a parameter list (which can be empty; see bellow for function parameters):

func printHello()
{
  print("Hello!");
}
Specifying a return type

Functions in ZScript can be made to return values to the caller by adding a : <type> bit after the parenthesis:

// Function that is guaranteed to return the non-randomly-chosen value 10 to the caller. Very useful when no randomness is expected
func nonrandom() : int // Specifies that the function returns an int
{
  return 10;
}

Return types are also the only valid place void is an accepted type, used to explicitly declare that a function returns no value:

// Handy shorcut function for when routinely printing 10: It saves you exactly 0 characters over writing 'print(10)', and was a standard coding practice back in the golden 'C' ages.
func print10() : void
{
  print(10);
  
  // Notice the lack of a 'return' keyword: returns are optional in void functions
}

Return types can be omitted, and when omitted, a return type of void is implied:

func example1() : void
{
  ...
}
func example2()
{
  ...
}

Both functions example1 and example2 above have a return value of void, with example2 being automatically set as void because it lacked an explicit return type.

The specification of a return type changes the semantics of return statements contained within the function: Non-void functions must specify a value in their return statements, while void functions do not allow for values to be specified:

func nonVoidFunc() : int
{
  return 0; // Removing the 0 value causes a compile-time error: Non-void functions require a return value!
}
func voidFunc() : void
{
  //return 0; // Uncommenting this line causes a compile-time error: Void functions cannot specify a return value!
}

As with the rest of the language, return values are typed, and are type-checked against the return type specified by the function signature.

Parameters

Functions can contain parameters, which are values that are specified by whomever is calling the function, and can be accessed like any variable:

func printNumber(n:int)
{
  print(n);
}

...
printNumber(10);
...

Prints

10

Functions can specify any number of parameters to be provided by separating them with commas:

func printSum(n1:int, n2:int)
{
  print(n1 + n2);
}

...
printSum(2, 2);
...

Prints

4

Parameters are typed values, and the type is used to check that the values being provided are valid:

func printNumber(n:int)
{
  print(n);
}

...
print(true); // Error: Function expects an int value, but receives a boolean value
...

When defining function parameters, it is to sometimes useful to omit a value and let the function decide what value to use instead. That is done with optional parameters, which are specified by adding = < value> in front of the parameter:

func increment(number:int, amount:int = 1) : int
{
  return number + amount;
}

...
print(increment(4));
print(increment(4, 2));
...

Prints

5
6

Note: In case a default value is provided, the type of the parameter can be inferred
Note: The default value of a function argument must be a constant (or 'compile-time') value

Functions can also specify a special parameter called variadic parameter which can receive any number of arguments passed by the caller. To create a variadic parameter, ... is added after the parameter's type:

func sumMany(values:int...) : int
{
  var sum = 0;
  for(var i = 0; i < values.Count; i++)
  {
    sum += values[i];
  }
  return sum;
}

...
print(sumMany(1, 2, 3, 4));
...

Prints

10

Variadic parameters are equivalent to lists: they can be subscripted and the count of values can be accessed with the .Count member.

Note: Variadic parameters cannot specify a default value

Things to consider with return statements:

Functions that have a return value require that all return statements specify a value:

func integerDivide(n1:int, n2:int) : int
{
  if(n2 != 0)
     return n1 / n2;
  
  return; // Invalid: all return statements require a value
}

In case the function specifies a return value, all possible code paths must return a value, that means that e.g. in an if/else condition, both sides must return values:

// Invalid: not all code paths return a value
func integerDivide(n1:int, n2:int) : int
{
  if(n1 != 0)
  {
    return n1 / n2;
  }
  else
  {
    print("Cannot divide by 0");
  }
}

Export functions

Export functions define functions not implemented by the script, but by the runtime executing the script. Export functions have the same semantics as ordinary functions, except in two aspects:

Definition

The definition of an export function is made by writing the function much the same way as a normal function, but replacing the func keyword with a special @ symbol:

// Function that queries the runtime for the current time in milliseconds
@getTime() : int

Export functions cannot specify a body, since the execution of the function is not realized by the script, but by the runtime executing the script.

Implementing Export Functions

Since export functions are executed at the runtime, a special object needs to be created in order to run the declared functions. The implementation is described in the following page:

<< TODO: Add link to implementation details of the ZScript runtime, and link the part about export functions here >>