Functions in Kotlin are declared with the fun keyword:
A function call goes in the traditional way:
If a function just returns a value of a single expression, one can specify its body after '=' and with no return statement:
In this case, it is allowed to omit the return type annotation, and the type will be inferred from the expression on the right-hand side:
If a function does not return any useful value, its return type is Unit. Unit is a type with only one value — Unit.VALUE. This value does not have to be returned explicitly:
Neither has the Unit return type to be specified explicitly:
If a function has a block body (in curly braces, not after '=') and returns Unit, one can omit the return type annotation:
Kotlin supports local functions, i.e. one can define a function inside a function:
Local functions can "see" local variables of outer functions (i.e. the closure), so we can have our visited set as a local variable, not a parameter to always pass around:
Local functions can even return from outer functions using qualified return expressions:
A member function is a function defined inside a class:
Member functions are called with a dot:
Functions may have generic parameters which can be specified in angle brackets after the function name and before the value parameters:
More on generic functions can be found on the page dedicated to generics.
The last argument of a function may be marked with vararg annotation:
By default, vararg creates an array, but this behavior can be customized by providing arguments to the annotation:
The type argument to the vararg annotation denotes a builder type. A call to this function is compiled like this:
So, the vararg builder must be a type that has
- A constructor that takes one Int parameter
- An add() function
- A build() function
The type of the vararg parameter if the returned type of build().
Function parameters may have default values, which are used when a corresponding argument is omitted. This allows to reduce the number of overloads, compared to Java:
This function may be called in three forms:
When a function has many parameters (and many of them have defaults), it is very convenient to see parameter names at the call site. Consider the following function in:
The best-case call site is fine:
But what if we want to change the word separator without changing the flags?
Without named arguments, a call site is hard to read. To document it, we can provide parameter names for each argument:
It is better already, but we can improve it: there's no need to specify all the arguments if some of the default values are fine for us:
If a member function (or an extension function) takes one parameter, it may be called in infix form, i.e. without a dot after receiver and parentheses around the arguments:
An infix call must have two parameters. One is not allowed to say, e.g. print 1, because there's nothing on the left of print.
Infix calls associate to the left, i.e. 1 foo 2 bar 3 means (1 foo 2) bar 3.
For precedence, see the grammar.
Extension functions allow us to define a function having some type as a receiver:
This function can be called on any Int with a dot, as if it were a member function:
For more details, see Extension functions.
A higher-order function is a function that takes functions as parameters, or returns a function. A good example of such a function is lock() that takes a lock object and a function, acquires the lock, runs the functions and releases the lock:
Let's examine the code above: body has a function type: () -> T, so it's supposed to be a function that takes no parameters and returns a value of type T. It is invoked inside the try block, while protected by the lock, and its result is returned by the lock() function.
If we want to call lock(), we can pass a function literal to it as an argument:
Function literals are described in details on this page. Here we only give a superficial description:
- a function literal is always surrounded by curly braces,
- its parameters (if any) are declared before -> (parameter types may be omitted),
- the body goes after ->.
In our case, there are no parameters, and the expression in the curly braces is the function literal body.
There is a convention in Kotlin that makes our example look nicer:
- if the last parameter of a function is itself a function, the corresponding function literal may be passed outside the parentheses:
This makes our code look more like a 'control structure'.
Another example of a higher order function would be map() (of Map/Reduce):
This function can be called as follows:
One other convention helps us here:
- if a function literal has only one parameter, its declaration may be omitted (along with the ->) and its name will be it:
These conventions allow us to write LINQ-like expressions:
Like named extension functions, function literals may declare receiver parameters. This allows us to write things like Groovy-style builders in a type-safe way.
Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure, i.e. those variables that are accessed in the body of the function. Memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead.
But it appears that in many cases this kind of overhead can be eliminated by inlining the function literals. The functions shown above are good examples of this situation. I.e., the lock() function could be easily inlined at call-sites. Consider the following case:
Instead of creating a function object for the parameter and generating a call, the compiler could emit the following code:
Isn't it what we wanted from the very beginning?
To make the compiler do this, one needs to annotate the lock() function with the inline annotation:
Inlining may cause the generated code to grow, but if we do it in a reasonable way (do not inline big functions) it will pay off in performance, especially at "megamorphic" call-sites inside loops.
|Inline functions are not implemented yet|
See the corresponding issue.
Sometimes we need to return several things from a function. Some languages support this through out-parameters, and some – through tuples. We follow the latter way.
Tuples are described in detail on this page. Here we just give an example: