A Comprehensive Kotlin Guide to Functions, Types, and Higher-Order Functions

A Comprehensive Kotlin Guide to Functions, Types, and Higher-Order Functions

Function

A group of instructions and expressions can be clubbed under one block named function, which can be reused by calling that function rather than writing the same bunch of expressions again and again.

The entry point of any Kotlin code is the main function: fun main() { /* .. */ }

Functions in Kotlin are declared using the fun keyword, followed by the type of inputs and outputs if any, and finally followed by the body of the function which comprises the expressions.

fun triple(x: Int): Int {
    var ans = x*3
    return ans
}

The above function triple takes an integer input which is stored in the variable x and returns an integer which is 3 times the value of x.

Functions in Kotlin are called using the function name followed by the invocation operator ().

var i = triple(10)
print(i)

A Function with no useful return value has the return type Unit. Return type declaration of such function is optional.

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
}
// same as
fun printHello(name: String?) { ... }

A Function can take a variable number of arguments/parameters of varying data types but can return only one value at a time. Value to these parameters must be passed when the function is invoked.

  • Named arguments can also be passed during the function call, which eliminates the need to pass arguments in the same order in which they are declared inside the function definition.

  • A default value can also be given to these parameters, which eliminates the compulsion of passing that particular value during the function call.

fun powerOf(number: Int, exponent: Int): Int { /*each parameter type
should be explicitily mentioned */ }

fun multiply(number: Int, multiplier: Int = 1): Int { /* here the 
default value 1 of multiplier will be taken when no value is passed for 
the multiplier during the function call */ }
fun reformat(
    str: String, normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*...*/ }

reformat( "String!", false, upperCaseFirstLetter = false,
    divideByCamelHumps = true, '_' )  //or
reformat("This is a long String!")    //or
reformat("This is a short String!", 
         upperCaseFirstLetter = false, wordSeparator = '_')

However, when overriding a function that has default values, the default parameter values must be omitted.

open class A {
    open fun foo(i: Int = 10) { /*...*/ }
}
class B : A() {
    override fun foo(i: Int) { /*...*/ }  // No default value is allowed.
}

Local Variable declaration can be skipped in some cases by simplifying the function declaration. This can be achieved by directly returning the result of the expression rather than devoting a variable to it.

fun triple(x: Int) : Int = x*3
fun triple(x: Int) = x*3 
fun triple(x: Int) : Int { return x*3 }

fun even(a: Int) : String = if(a%2==0){
    "Even"    }
    else{
    "Odd"    }

Local Functions - are functions that are inside other functions. A local function can access local variables of outer functions

Member Functions - are functions that are defined inside a class or object. Member functions are called using dot notation.

class Sample {
    fun greet() { print("Hello") }
}
Sample().greet() // creates instance of class Sample and calls greet

Lambda Expression & Anonymous Functions

Functions that are not declared but are passed immediately as an expression are called Function Literals i.e. Functions without a name. Lambda Expression and Anonymous Function are types of Function Literals.
Hence these function literals are stored inside variables and are mainly used in higher-order functions.

Syntax - (parameter1Type, parameter2Type...) -> returnType = { functionBody }

Let us suppose we have a function that returns if string b is greater than a.

fun compare(a: String, b: String): Boolean = a.length < b.length
/* we can form the lambda expression as: 
{ a , b -> a.length < b.length } */

//hence we can write the function as: 
val compare : (String,String) -> Boolean = { a,b -> a.length>b.length}
//or
val compare = {a: String, b: String -> a.length > b.length}
fun square(a: Int) : Int { return a }
val square : (Int) -> Int = { x -> x*x }
val square = { x: Int -> x*x}

val add = {x: Int, y: Int -> x+y}

Lambda expressions are useful when passing a function as a parameter to another function ( Higher Order Functions). According to the Kotlin Convention, the Lambda Expression can be mentioned outside the invocation operator () during the function call.
If lambda expression is the only argument, then the () can be omitted during the function call.

fold(1,"a") { acc, e -> acc * e }
run { println("...") }
  • If the lambda expression has only one parameter, the declaration of the parameter and -> can be omitted. The parameter is implicitly declared under the name it.

      filter { it > 0 } // (it: Int) -> Boolean
    

Higher Order Functions

Functions in Kotlin can be stored inside variables and data structures and can be passed as arguments and returned from higher-order functions.

A higher-order function is a function that takes a function as parameters or returns a function.
eg: fold function for collections in Kotlin

val items = listOf(1, 2, 3, 4, 5)
items.fold(0, { 
    acc: Int, i: Int -> 
    print("acc = $acc, i = $i, ") 
    val result = acc + i
    println("result = $result")
    // The last expression in a lambda is considered the return value:
    result
})

// Parameter types in a lambda are optional if they can be inferred:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })

//produces output: 
acc = 0, i = 1, result = 1
acc = 1, i = 2, result = 3
acc = 3, i = 3, result = 6
acc = 6, i = 4, result = 10
acc = 10, i = 5, result = 15
joinedToString = Elements: 1 2 3 4 5