Types of available functions in Kotlin
--
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…