Kotlin Functions

Overview

First, let’s look at an example that defines a simple function.

fun hello(name: String) {
  println("Hello, ${name}!")
}

The example above defines the hello function. In Kotlin, functions are defined with the fun keyword. Write the fun keyword, the function name, and the argument list in that order. Each argument must specify a type.

Functions usually belong to classes, but they do not always have to. They can also be defined at the top level.

Functions That Return Values

Let’s define a function that performs a calculation with arguments and returns the result. The following example defines a sum function that performs simple addition.

fun sum(a: Int, b: Int): Int {
    return a + b
}

The difference from the earlier hello function is that this function specifies a return type and returns a value with the return keyword. The return type is written immediately after the function’s argument list, separated by a colon (:). Because the return value of the sum function is the result of adding two Int values, the return type is also Int. The return keyword returns the function’s value.

A function that has a return value must specify the type at the end and return the value with the return keyword.

Single Expression Functions

Functions that can be written in one line can also be defined by returning the value directly with =.

fun sum(a: Int, b: Int) = a + b

Default Arguments

Function arguments can have default values.

fun hello(name: String, exclamation: Boolean = false) {
  val suffix = if (exclamation) "!" else ""
  println("Hello, ${name}${suffix}")
}

The hello function has an argument named exclamation of type Boolean, and a default value is set for it. An argument with a default value, also called a default argument, can be omitted when calling the function. If it is omitted, the default value is used.

Example: Using a function with a default argument

// Omit the second argument
hello("Kotlin")       // Hello, Kotlin

// Specify the second argument
hello("Kotlin", true) // Hello, Kotlin!

When calling a function, you can also pass values by specifying argument names.

Example: Named arguments

hello(name = "Foo")

// You do not have to follow the order of the argument list.
hello(exclamation = false, name = "Baz")

Default arguments can be passed only to the arguments you want to specify by writing the argument names when calling the function.

fun drawCircle(x: Int, y: Int, r: Int,
    lineColor: String = "#000",
    fillColor: String = "transparent",
    borderWidth: String = "1px") {
  ... omitted ...
}

fun main() {
    drawCircle(100, 100, 50, borderWidth="2px")
}

Unit Functions

Functions that do not return a value are treated as Unit functions.

fun printMessage(msg: String): Unit {
    println(msg)
}

Unit is usually omitted as shown below.

fun printMessage(msg: String) {
    println(msg)
}

Nothing Type Functions

Functions that always throw an exception and cannot return a value are defined as functions of type Nothing.

fun raiseError(msg: String): Nothing {
    throw MyException(msg)
}

Variable Number of Arguments

vararg declares a variable number of arguments.

fun foo(vararg args: String) {
    args.forEach {
        println(it)
    }
}

fun main() {
    foo("A", "B", "C")
}

When passing an array variable to a function with a variable number of arguments, you must expand and pass the array with the spread operator (*).

fun main() {
    val arr = arrayOf("A", "B", "C")
    foo(*arr)
}

Function References (::)

Putting :: before a function name gives you an object that references the function. In the example below, the add function is assigned to the function object method and then called.

fun add(x: Int, y: Int): Int = x + y

fun main() {
    val method = ::add
    println(method(3, 5))
}

Recursive Calls

When a function calls itself, it is called a recursive call. Recursive calls let you write loops declaratively. For example, let’s look at a function that returns the sum of a list of arguments. First, here is the normal version.

Example: A loop with for

fun sum(list: List<Int>): Int {
  var sum = 0
  for (e in list) {
    sum += e
  }
  return sum
}

This loops with for. The variable sum is declared with var, and a new value is assigned repeatedly.

Next is the recursive-call version.

Example: A loop with a recursive call

fun sum(list: List<Int>): Int =
    if (list.isEmpty()) 0 else list.first() + sum(list.drop(1))

This version no longer reassigns values with for. Instead, the sum function calls itself in its definition.

For reference, isEmpty(), first(), and drop(1) are methods of list, which is a list of integers. They return whether the list is empty, the first element of the list, and a new list with the first element removed, respectively.

Let’s write a function that finds the greatest common divisor.

// Return the larger number.
fun max(a: Int, b: Int): Int = if (a < b) b else a

// Return the smaller number.
fun min(a: Int, b: Int): Int = if (a <= b) a else b

// Return the greatest common divisor.
fun gcd(a: Int, b: Int): Int {
  var x = max(a, b)
  var y = min(a, b)
  while(y != 0) {
    val w = y
    y = x % y
    x = w
  }
  return x
}

Now let’s reimplement the gcd function with recursive calls.

Example: Making gcd a recursive function

fun gcd(a: Int, b: Int): Int {
  val x = max(a, b)
  val y = min(a, b)
  return if (y == 0) x
         else gcd(y, x % y)
}

The var, the while loop, and even the temporary variable w can be removed. In many cases, recursive calls make code cleaner and easier to read.

The disadvantage of recursive calls is that they call the function many times and consume the stack. If a function keeps being called repeatedly, depending on the environment, a stack overflow can occur and the program can crash. To avoid this, Kotlin provides a mechanism called tail call optimization.

A tail call is a call where the recursive call is at the end. For example, the gcd function in the example just above performs a tail call. If you add the tailrec keyword to such a recursive function, tail call optimization is applied and the function is transformed into code that does not cause a stack overflow.

Example: A gcd function where tail call optimization is effective

tailrec fun gcd(a: Int, b: Int): Int {
  val x = max(a, b)
  val y = min(a, b)
  return if (y == 0) x
         else gcd(y, x % y)
}

Because optimization is effective only for tail calls, you need to understand how recursion works depending on the function.