Kotlin Functions
In the previous article we talked about classes and how the concepts of inheritance is applicable to Kotlin classes. In this article we are going to talk about functions in Kotlin and how to use and modify them on basic level.
Functions
Functions (our methods in Java) are declared by using the fun keyword:
fun onCreate(savedInstanceState: Bundle?) { }
Functions in Kotlin always return a value. So if you don’t specify a return value, it will return Unit. Unit is similar to void in Java, though this is in fact an object. You can, of course, specify any type as a return value:
fun add(x: Int, y: Int): Int { return x + y }
However, if the result can be calculated using a single expression, you can get rid of brackets and use equal:
fun add(x: Int, y: Int) : Int = x + y
Explicitly declaring the return type is optional when this can be inferred by the compiler:
fun double(x: Int) = x * 2
Parameters
Function parameters are defined using Pascal notation, i.e. name: type. Parameters are separated using commas. Each parameter must be explicitly typed:
fun powerOf(number: Int, exponent: Int) { ... }
Default Arguments
An extremely useful thing about parameters is that we can make them optional by specifying a default value. Here it is an example of a function you could create in an activity, which uses a toast to show a message:
fun toast(message: String, length: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, message, length).show() }
As you can see, the second parameter (length) specifies a default value. This
means you can write the second value or not, which prevents the need of function overloading:
toast(“Hello”) toast(“Hello”, Toast.LENGTH_LONG)
This would be equivalent to the next code in Java:
void toast(String message){ toast(message, Toast.LENGTH_SHORT); } void toast(String message, int length){ Toast.makeText(this, message, length).show(); }
And this can be as complex as you want. Check this other example:
fun niceToast(message: String, tag: String = MainActivity::class.java.simpleName, length: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, “[$tag] $message”, length).show() }
I’ve added a third parameter that includes a tag which defaults to the class name. The amount of overloads we’d need in Java grows exponentially. You can now write these calls:
niceToast(“Hello”) niceToast(“Hello”, “MyTag”) niceToast(“Hello”, “MyTag”, Toast.LENGTH_SHORT)
And there is even another option, because named arguments can be used, which means you can write the name of the argument preceding the value to specify which one you want. By doing this we are making the code more readable and at the same time we are just passing the values which have to be explicitly defined and rest of the arguments will take their default values.
niceToast(message = “Hello”, length = Toast.LENGTH_SHORT)
When a function is called with both positional and named arguments, all the positional arguments should be placed before the first named one. For example, the call f(1, y = 2) is allowed, but f(x = 1, 2) is not.
Overriding methods always use the same default parameter values as the base method. When overriding a method with default parameters values, the default parameter values must be omitted from the signature:
open class A { open fun foo(i: Int = 10) { ... } } class B : A() { override fun foo(i: Int) { ... } // no default value allowed }
Tip: String templates You can use template expressions directly in your strings. This will make it easy to write complex strings based on static and variable parts. In the previous example, I used “[$className] $message”. As you can see, anytime you want to add an expression, just write the $ symbol. If the expression is a bit more complex, you can add a couple of brackets: “Your name is ${user.name}”.
Variable number of arguments (Varargs)
A parameter of a function (normally the last one) may be marked with vararg modifier:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
allowing a variable number of arguments to be passed to the function:
val list = asList(1, 2, 3)
Only one parameter may be marked as vararg. If a vararg parameter is not the last one in the list, values for the following parameters can be passed using the named argument syntax, or, if the parameter has a function type, by passing a lambda outside parentheses.
When we call a vararg-function, we can pass arguments one-by-one, e.g. asList(1, 2, 3), or, if we already have an array and want to pass its contents to the function, we use the spread operator (prefix the array with *):
val a = arrayOf(1, 2, 3) val list = asList(-1, 0, *a, 4)
Infix notation
Functions marked with the infix keyword can also be called using the infix notation (omitting the dot and the parentheses for the call). Infix functions must satisfy the following requirements:
They must be member functions or extension functions; They must have a single parameter; The parameter must not accept variable number of arguments and must have no default value.
infix fun Int.shl(x: Int): Int { // ... } // calling the function using the infix notation 1 shl 2 // is the same as 1.shl(2)
Infix function calls have lower precedence than the arithmetic operators, type casts, and the rangeTo operator. The following expressions are equivalent: 1 shl 2 + 3 and 1 shl (2 + 3) 0 until n * 2 and 0 until (n * 2) xs union ys as Set<> and xs union (ys as Set<>) On the other hand, infix function call’s precedence is higher than that of the boolean operators && and ||, is- and in-checks, and some other operators. These expressions are equivalent as well: a && b xor c and a && (b xor c) a xor b in c and (a xor b) in c See the Grammar reference for the complete operators precedence hierarchy.
Note that infix functions always require both the receiver and the parameter to be specified. When you’re calling a method on the current receiver using the infix notation, you need to use this explicitly; unlike regular method calls, it cannot be omitted. This is required to ensure unambiguous parsing.
class MyStringCollection { infix fun add(s: String) { /* ... */ } fun build() { this add "abc" // Correct add("abc") // Correct add "abc" // Incorrect: the receiver must be specified } }
Function Scope
On the basis of scope functions can be of two types:
Local functions: function inside a function
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
Member function: function inside a class and called using dot (.) operator.
class Sample() { fun foo() { print("Foo") } }
Tail recursive functions
Kotlin supports a style of functional programming known as tail recursion. This allows some algorithms that would normally be written using loops to instead be written using a recursive function, but without the risk of stack overflow. When a function is marked with the tailrec modifier and meets the required form, the compiler optimises out the recursion, leaving behind a fast and efficient loop based version instead:
tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
There are many other types of functions like Generic Functions, Inline Functions, Extension Functions and Higher-Order Functions and Lambdas which are of advanced use. That covered the basics of functions and uses in Kotlin.
Comment below if something needs to be corrected.
References:
- Official Kotlin Documentation
- Book — Kotlin for android developers by Antonio Levia