Types of available functions in Kotlin

Abhishek Srivastava
17 min readDec 25, 2022

--

In this article, you’ll learn about types of functions available in Kotlin. so if you don’t know what is function then first read this documents before start it.

Types of Functions

  • Kotlin inline functions
  • Kotlin infix functions
  • Kotlin operator functions
  • Kotlin Recursion function
  • Kotlin Tail Recursive function
  • Kotlin Extension Function
  • Kotlin High-Order Functions
  • Kotlin Lambdas Functions
  • Kotlin Anonymous Function

1. Kotlin inline functions

An inline function in Kotlin works like a preprocessor macro. The compiler takes all occurrences of calls to that inline function and replaces them with the actual function body, much like how the C preprocessor replaces all occurrences of the macro with the macro definition.


inline fun max(x: Int, y: Int): Int = if (x > y) x else y

fun main() {
val bigger = max(3, 7)

println("The larger of 3 and 7 is $bigger")
}

The compiler does not generate an actual function, despite our declaration. Rather, each place that we call the function, Kotlin will compile the code from the function body. It will be as if we wrote:

fun main() {
val bigger = if (3 > 7) 3 else 7
println("The larger of 3 and 7 is $bigger")
}

We gain some performance by avoiding an actual function call. But, if we call max() in many places, we wind up with many copies of the max() code in the compiled app, and our app will be bigger.

If you want to know about generic inline, You can read from here.

The use of inline function is that, The CPU does not allocate the memory for the function and just copy the body of the function on the calling place. So by using this we reduce the memory overhead as well.

You can read more inline function from here or use below link
https://agrawalsuneet.github.io/blogs/inline-function-kotlin/

2. Kotlin infix functions

In kotlin we have one word concept of infix function which is actually the Member function or the extension function in case of Kotlin. So the infix function can be a Member function or the extension function.Kotlin have two types of infix function notation in Kotlin-

  • Standard library infix function notation.
  • User defined infix function notation.`

Standard library infix function notation

When we use operators like and, or , shr, shl etc then compiler looks for the function and calls the desired one.

Koltin program using bitwise and operator –

fun main(args: Array<String>) {
var a = 15
var b = 12
var c = 11
// call using dot and parenthesis
var result1 =(a > b).and(a > c)
println("Boolean result1 = $result1")
// call using infix notation
var result2 =(a > b) and (a > c)
println("Boolean result1 = $result2")
}
Output
Boolean result1 = true
Boolean result1 = true

User defined infix function notation –

In kotlin we can create own function with infix notation if the function satisfy the following requirements:

  • It must be member function or extension function.
  • It must accepts a single parameter.
  • Parameter must have no default value and must not accept variable number of arguments.
  • It must be marked with infix keyword.

Example:

class math {
// user defined infix member function
infix fun square(n : Int): Int{
val num = n * n
return num
}
}
fun main(args: Array<String>) {
val m = math()
// call using infix notation
val result = m square 3
print("The square of a number is: "+result)
}
Output
The square of a number is: 9

Key points

  • Infix functions are declared similarly to extension functions
  • They are declared by writing infix fun at the start of the function definition
  • When applied like 1 plus 2, the function belongs to the variable on the left (1) and the variable on the right (2) is the function parameter
  • Can be used to create DSLs
  • They must have a single parameter
  • The parameter must not accept variable number of arguments and must have no default value

3. Kotlin operator functions

It’s the concept of Operator overloading but its beyond that in kotlin.

Kotlin allows us to overload some operators on any object we have created, or that we know of (through extensions). The concept of operator overloading provides a way to invoke functions to perform arithmetic operation, equality checks or comparison on whatever object we want, through symbols like +, -, /, *, %, <, >. But, obviously, those overloading should be defined when it make sense to use them.

For the following parts, let’s assume we have the data class:

data class Point(val x: Double, val y: Double)

Arithmetic operators

To overload the + operator we need to implement the function plus, with the keyword operator. This function takes one parameter of any kind, even it make sense in most cases to use the same type.

// Here how to provide `+` operator on our object Point
operator fun plus(p: Point) = Point(this.x + p.x, this.y + p.x)
// return type is inferred to Point

To go further we can apply all the following operator overloading on the object Point.

Here the implementation on our previous data class:

data class Point(val x: Double, val y: Double) {
operator fun plus(p: Point) = Point(x + p.x, y + p.y)
operator fun minus(p: Point) = Point(x - p.x, y - p.y)
operator fun times(p: Point) = Point(x * p.x, y * p.y)
operator fun div(p: Point) = Point(x / p.x, y / p.y)
operator fun inc() = Point(x + 1, y + 1)
operator fun dec() = Point(x - 1, y - 1)
}

If you run it, Then the output will be:

Running Point(x=2.0, y=3.0) + Point(x=3.0, y=2.0) = Point(x=5.0, y=5.0)
Running Point(x=2.0, y=3.0) - Point(x=3.0, y=2.0) = Point(x=-1.0, y=1.0)
Running Point(x=2.0, y=3.0) * Point(x=3.0, y=2.0) = Point(x=6.0, y=6.0)
Running Point(x=2.0, y=3.0) / Point(x=3.0, y=2.0) = Point(x=6.0, y=6.0)
Running Point(x=2.0, y=3.0) ++ = Point(x=3.0, y=4.0)
Running Point(x=3.0, y=2.0) -- = Point(x=2.0, y=1.0)

Why operator overloading is useful

Operator overloading is useful because it allows Kotlin operators to have meanings for classes and types that you define in your source code.

Instead of just throwing an error because it doesn’t know what to do with your data type, operator overloading allows you to integrate custom-defined types as if they were built-in types.

Another example where operator overloading comes in handy is when you have a custom data type involving geometric shapes.

Suppose you have a Rectangle shape with length and width properties as shown below:

data class Rectangle(length: Int, width: Int)

You can override the unary minus operator - to transform both length and width properties to their equivalent negative values.

Add the unaryMinus() function to your data class definition as shown below:

data class Rectangle(val length: Int, val width: Int) {
operator fun unaryMinus(): Rectangle {
return Rectangle(-this.length, -this.width)
}
}

Note that unaryMinus() is the function called when you place the minus operator before the variable name.

If you place the minus operator after the variable name, you need to overload the minus() function.

Here’s an example of calling unaryMinus() from Rectangle class:

val rec = Rectangle(8, 5)
val recMinus = -rec
println(recMinus.length) // -8
println(recMinus.width) // -5

Without operator overloading, you have to manually re-assign the property values.

Kotlin only allows you to overload a pre-defined set of operators. You can refer to Kotlin operator overloading documentation for a full list of operators that you can overload with their specific rules.

You can read operator overloading concept from here or read the medium blog for complete detail also from here.

4. Kotlin Recursion function

A recursive function is defined as a function that is implemented in such a way that it calls itself. This process of repeatedly calling a function is called recursion. Every recursive function should have a terminate/end condition; otherwise, the program execution will enter into an infinite loop. In this case, the program leads to a stack overflow error.

Kotlin Program to calculate factorial of a number using recursion

The following is a simple program to calculate the factorial of a number using recursion.

The factorial of a positive number is defined as follows:

Factorial(x) = x * (x-1) * (x-2) * ……… * 3 * 2 * 1

It is also denoted by x!

// Kotlin program to calculate factorial of a number using recursion
fun Factorial(num: Int):Long{
if(num == 1)
return num.toLong()
return num*Factorial(num-1) // recursive call
}
fun main() {
val num: Int = 6 // Enter the number
println("Factorial of $num is ${Factorial(num)}")
}
Output:
Factorial of 6 is 720

Notice that functions are being called first. Then, calculations are being done in last step.

Compiler maintains each function call using stack. So, calling function in this way consumes more space. Your program may even throw stackoverflow error if functions are being called too many times.

The recursive function must have a breaking condition to stop the recursion; else it will run indefinitely.

Importance of Base Case

The base case or the terminal condition is a very important condition in any recursive function. This condition tells the program where it needs to stop the execution. If this condition is not specified, the program will run into an infinite loop. For example, consider the example of calculating the factorial of a number using recursion as discussed above.

The condition:-

if(num == 1)
return num.toLong()

is the base condition in that program. If we remove this condition, the program will loop infinitely. Refer to the blog Kotlin Decision Statements for more understanding about this syntax.

// Kotlin recursive program without base case
// StackOverflow Error
fun Factorial(num: Int):Long{
return num*Factorial(num-1) // no terminate condition
}
fun main() {
val num: Int = 6
println("Factorial of $num is: ${Factorial(num)}")
}

Output:

Exception in thread "main" java.lang.StackOverflowError

You can also cehck about the DeepRecursive from Kotlin official document.

5. Kotlin Tail Recursive function

Tail recursion the computation is done at the beginning before the recursive call. In tail recursion the call to the recursive function occurs at the end of the function. Which means the computation is done first and then passed to the next recursive call.

Lets take an example of tail recursion.

To declare a tail recursive function we use tailrec modifier before the function.

fun main(args: Array<String>) {
val number = 6
val factorial = fact(number)
println("Factorial of $number = $factorial")
}
tailrec fun fact(n: Int, temp: Int = 1): Int {
return if (n == 1){
temp
} else {
fact(n-1, temp*n)
}
}

Output:

The keyword tailrec tells the compiler that the implementation of the function is required to be tail-recursive, and causes the compiler to report an error if the function is not actually tail-recursive. It protects the user from situations when a change to the implementation of the function causes it to no longer be tail-recursive, and causes an unexpected drop in performance (or a complete failure in production due to a stack overflow error).

You can read Recursive and Tail Recursive from here.

6. Kotlin Extension Function

Kotlin provide the feature of add new functionality to a class without use of the concept of inheritance This feature is called Extension Function.

The purpose of extension function is to add a new function to the existing class. Simply adds a particular function to a predefined class without declaring it inside the class and the new functions actually behaves just like the static functions in case of Java now practically also during the compilation the kotlin compiler also converts these functions into the static functions internally that happens in background.

// A SAMPLE CLASS TO DEMONSTRATE EXTENSION FUNCTIONS
class Circle (val radius: Double){
// MEMBER FUNCTION OF CLASS
fun area(): Double{
return Math.PI * radius * radius;
}
}
fun main(){
// EXTENSION FUNCTION CREATED FOR A CLASS CIRCLE
fun Circle.perimeter(): Double{
return 2*Math.PI*radius;
}
// CREATE OBJECT FOR CLASS CIRCLE
val newCircle = Circle(2.5);
// INVOKE MEMBER FUNCTION
println("Area of the circle is ${newCircle.area()}")
//INVOKE EXTENSION FUNCTION
println("Perimeter of the circle is ${newCircle.perimeter()}")
}

Output is:
Area of the circle is 19.634954084936208
Perimeter of the circle is 15.707963267948966

Advantages

  • The extension function has few properties such as they can become part of your user defined class (example, student, employee ,user and so on) and also become part of the predefined classes such as array, string, integer or any other predefined classes that comes with the Kotlin SDK.
  • An extension declared inside the class then it can access other private member of the same class.
  • If an extension is declared outside the class, it can not access other private member of the same class.

Extended library class using extension function

Kotlin also allows library classes to be extended. For this, the extension function must be added to library classes and used in the same way as for user-defined classes.

Fun main(){
// Extension function for Int type
fun Int.abs() : Int{
return if(this < 0) -this else this
}
println((-4).abs())
println(4.abs())
}

Output:
4
4

Companion Object Extensions

In kotlin we can also define extension functions and properties for the companion object.

class MyClass {
// companion object declaration in kotlin
companion object {
fun display(){
println(“Companion object")
}
}
}
fun main(args: Array<String>) {
// invoking member function
val ob = MyClass.display()
}

Output:
companion object

As we calling the regular member function of the companion object then we can call extension function using only the class name as the qualifier.

Companion object extension example

class MyClass {
companion object {
// member function of companion object
fun display(str :String) : String{
return str
}
}
}
// extension function of companion object in kotlin
fun MyClass.Companion.abc(){
println("Extension function of companion object")
}
fun main(args: Array<String>) {
val ob = MyClass.display("Function declared in companion object")
println(ob)
// invoking the extension function
val ob2 = MyClass.abc()
}
Output:
Function declared in companion object
Extension function of companion object

7. Kotlin High-Order Functions & Lambdas

In Kotlin, a higher-order function takes another function as an argument or returns another function. Lambda expressions are frequently supplied as arguments to or returned from higher-order functions. Kotlin Higher-Order Function can likewise use an anonymous function for this purpose.

You can read also from kotlin Official document from here.

We’ll see a few examples of passing functions and lambdas as arguments or returning them from another function.

import java.io.File

class Calculator{
fun add(a: Int, b: Int): Int{
return a+b
}

fun subtract(a: Int, b: Int): Int{
return a-b
}
}

fun main(args: Array) {
var a=8
var b=6
var calc:Calculator = Calculator()
println(calc.add(a,b))
println(calc.subtract(a,b))
println(calc.multiply(a,b))
}
// multiply is an extension function to the class Calculator
fun Calculator.multiply(a: Int, b: Int): Int{
return a*b
}

Output:

14
2
48

How Kotlin extensions work

Kotlin extensions are resolved statically. This means that the extended function to call is determined by the type of the expression on which it is invoked at compile-time, rather than on the type resulting from evaluating that expression at runtime.

Let’s understand this better with an example:

open class Car
class Convertible: Car()

// defining the getType() extensions function on Car
fun Car.getType() = "Generic car"
// defining the getType() extensions function on Convertible
fun Convertible.getType() = "Convertible car"

fun getCarType(car: Car) : String {
return convertible.getType()
}

fun main() {
print(getConvertibleType(Convertible()))
}
This would print:

Generic car

In OOP (Object-Oriented Programming) logic, you would expect this to print “Convertible car.” Well, this is not what happens when using extension functions. In fact, the getType() extension function called is the one coming from the declared type of the car parameter known at compile-time, which is the Car class.

Also, you should know that Kotlin extensions are usually defined on the top-level scope, directly under package and import lines:

package com.logrocket.extensions
fun MutableList<String>.concatenateLowercase() : String {
return this.map{ s -> s.lowercase() }.joinToString("")
}

Then, if you need to use it outside its declaring package, you can import it as you would with any external dependency:

package com.logrocket.example
// importing the extension function defined in
// the com.logrocket.extensions package
import com.logrocket.extensions.concatenateLowercase
fun main() {
val list = mutableListOf("First", "seConD", "ThIRd")
list.concatenateLowercase()
}

Finally, you have to be aware that Kotlin extensions can also be defined on nullable types. Consequently, Kotlin extension functions can be called on an object variable even when its value is null.

Popular uses of Kotlin extensions

Kotlin extensions are not limited to functions. On the contrary, this is a versatile and effective mechanism that allows you to achieve endless results. Let’s now delve into its most popular uses.

Extension functions

This is the most common use of the Kotlin extension feature. As you have already seen, adding an extension function to a class is very easy, and can be achieved as follows:

fun Int.doubleValue() : Int {
return this * 2
}

In this way, any Int instance will now have a doubleValue() function that returns twice its current value. This was defined by taking advantage of the special this keyword. With it, you can access the instance of the object with the type as resolved statically, and use it to implement your desired logic.

Extension properties

With Kotlin extensions, you can also add a new property to an existing class. Kotlin extension properties can be defined as shown in the example below:

val <T> List<T>.penultimateElement: T?
get() = if (size < 1)
null
else
list.get(size - 2)

Such a property allows you to easily retrieve the penultimate element of a list, if present. Let’s now see how to access it:

val list = mutableListOf("first", "second", "third")
print(list.penultimateElement)

This would show the following text in your console:
second

As you can see, this new extension property can be accessed like any other normal property. The main difference with them is that extension properties cannot have initializers. This means that their value can only be handled by explicitly providing getters and setters.

8. Kotlin Lambdas Functions

Lambda in kotlin is a type of function which has no name. This function is defined with a curly braces {} which takes variable as a parameter (if any) and body of function. Here body of function is written after variable (if any) followed by -> operator.

Syntax

{ variable-> body_of_function}

Example

un main(args: Array<String>){  
val myLambdaFunction: (Int) -> Unit= {n: Int -> println(n) }
addNumberFunction(50,30,myLambdaFunction)
}
fun addNumberFunction(a: Int, b: Int, mylambda: (Int) -> Unit ){
val add = a + b
mylambda(add) // println(add)
}

A lambda expression have optional type annotations, always surrounded by curly braces and argument declarations is define inside curly braces . the function_body goes after an arrow -> sign. the last expression inside the lambda body is treated as return value if the inferred return type of the lambda is not Unit

Example:

val subtract = {n: Int , m: Int -> n - m}

In Kotlin, the lambda expression also have optional part except fuction_body. Here is the example of lambda expression.

val subtract:(Int,Int) -> Int = { n, m -> n - m}

Note: In this we don’t need a variable because variable can be passed to a function directly as an argument .

Kotlin program of using lambda expression

//  lambda expression with type annotation 
val sumOne = { n: Int, m: Int -> n + m }
// lambda expression without type annotation
val sumTwo:(Int,Int)-> Int = { n , m -> n + m}
fun main(args: Array<String>) {
val result1 = sumOne(2,3)
val result2 = sumTwo(3,4)
println("The sum of two numbers is: $result1")
println("The sum of two numbers is: $result2")
// directly print the return value of lambda
// without storing in a variable.
println(sum1(5,7))
}

Output:
The sum of two numbers is: 5
The sum of two numbers is: 7
12

Type inference in lambdas

Type inference in kotlin helps the compiler to evaluate lambda expression’s type. Here is the lambda expression by which we can calculate the sum of two integers.

val sum = {n: Int , m: Int -> n + m}

Here, Compiler of kotlin consider that it as a function which have two parameters type of Int and this function return Int value.

(Int,Int) -> Int

If we want to return String value from it than we can do it by the using of toString() inbuilt function.

val sumOne = { a: Int, b: Int ->
val add = n + m
add.toString() //convert Integer to String
}
fun main(args: Array<String>) {
val result1 = sumOne(2,3)
println("The sum of two numbers is: $result1")
}

Output:
The sum of two numbers is: 5

In this program, Compiler of kotlin evaluate that it is a function which takes two integer values and this function will return String.

(Int,Int) -> String

Type declaration in lambdas

In kotlin we can explicitly define the type of the lambda expression. If lambda does not returns value then we can use: Unit

Pattern: (Input) -> Output

Here are lambdas examples with return type

val lambda1: (Int) -> Int = (a -> a * a)
val lambda2: (String,String) -> String = { a , b -> a + b }
val lambda3: (Int)-> Unit = {print(Int)}
We can also use lambdas as class extension:
val lambda4: String.(Int) -> String = {this + it}

Kotlin program for lambdas used as class extension

val lambda : String.(Int) -> String = { this + it }
fun main(args: Array<String>) {
val result = "Codinglance".lambda(150)
print(result)
}

Output:
Codinglance150

Implicit name of a single parameter

In severals cases lambdas have the single parameter. Where, it is used to represent the single parameter that we pass to lambda expression.

Kotlin program by the using of shorthand form of lambda function

al numbers = arrayOf(1,-2,3,-4,5)
fun main(args: Array<String>) {
println(numbers.filter { it > 0 })
}

Output:
[1, 3, 5]

Kotlin program by the using of longhand form of lambda function

val numbers = arrayOf(1,-2,3,-4,5)
fun main(args: Array<String>) {
println(numbers.filter {item -> item > 0 })
}

Output
[1, 3, 5]

Returning a value from lambda expression

After running of the lambda it will return the final value.String, Integer or Boolean these value can be returned by the lambda function.

Example

val find =fun(num: Int): String{
if(num % 2==0 && num < 0) {
return "Number is even and negative"
}
else if (num %2 ==0 && num >0){
return "Number is even and positive"
}
else if(num %2 !=0 && num < 0){
return "Number is odd and negative"
}
else {
return "Number is odd and positive"
}
}
fun main(args: Array<String>) {
val result = find(112)
println(result)
}

Output:
Number is even and positive

9. Kotlin Anonymous Function

An anonymous function in kotlin is very similar to regular function except that the name of the function which can be omitted from the declaration of the fuctions. The body of the anonymous function can be an expression or block.

Example 1: Function body as an expression

fun(n: Int, m: Int) : Int = n * m

Example 2: Function body as a block

fun(n: Int, m: Int): Int {
val multiply = n * m
return multiply
}

Thanks for reading…

--

--

Abhishek Srivastava

Senior Software Engineer | Android | Java | Kotlin | Xamarin Native Android | Flutter | Go