Local Functions in Kotlin
Last Updated :
22 Feb, 2022
The idea behind functions is very simple: split up a large program into smaller chunks that can be reasoned more easily and allow the reuse of the code to avoid repetition. This second point is known as the DRY principle: Don’t Repeat Yourself. The more the number of times you write the same code, the more the chances you create of a bug creeping in. When this principle is taken to its logical conclusion, you would have created a program that consists of many small functions, each doing a single thing; this is similar to the Unix principle of small programs, where each program does a single job. The same principle applies to the code inside a function. Typically, in say Java, a large function or method might be broken down by calling several support functions declared in either the same class or a helper class that contains static methods.
Example
Kotlin allows us to take this a step further by supporting functions declared inside other functions. These are called local or nested functions. Functions can even be nested multiple times. The example of printing areas can be written in the following style:
Kotlin
fun printArea(width: Int, height: Int): Unit {
fun calculateArea(width: Int, height: Int): Int = width * height
val area = calculateArea(width, height)
println( "The area is $area" )
}
|
As you can see, the calculateArea function is now inside printArea and thus not accessible to the code outside. This is useful when we want to hide functions that are just used as implementation details of a larger function. We could also achieve a similar effect by defining a member function as private. So do local functions have any other advantages? Yes, they do! Local functions can access the parameters and variables defined in the outer scope:
Kotlin
fun printArea2(width: Int, height: Int): Unit {
fun calculateArea(): Int = width * height
val area = calculateArea()
println( "The area is $area" )
}
|
Notice that we’ve removed the parameters from the calculateArea function, and now it directly uses the parameters defined in the enclosing scope. This makes the nested function more readable and saves repeating the parameter definitions, which is very useful for functions with many parameters. Let’s work through an example of a function that could be broken down using local functions:
Kotlin
fun fizzbuzz(start: Int, end: Int): Unit {
for (k in start..end) {
if (k % 3 == 0 && k % 5 == 0 )
println( "Fizz Buzz" )
else if (k % 3 == 0 )
println( "Fizz" )
else if (k % 5 == 0 )
println( "Buzz" )
else
println(k)
}
}
|
This is the well-known Fizz Buzz problem. The requirement asks you to print out the integers from the start to the end value. However, if the integer is a multiple of 3, you should print Fizz. If it is a multiple of 5, you should print Buzz. If it is a multiple of 3 and 5, then print Fizz Buzz together.
The first solution is short and readable, but it duplicates some code. The modulo checks are coded twice, which doubles the potential for a bug. Clearly, this example is extremely simple, so the chances of a typo are minimal; however, it serves to demonstrate the issue for larger problems. We can declare a local function for each of the modulo checks so that we only have to code it once. This brings us to the next iteration of our solution:
Kotlin
fun fizzbuzz2(start: Int, end: Int): Unit {
fun isFizz(k: Int): Boolean = k % 3 == 0
fun isBuzz(k: Int): Boolean = k % 5 == 0
for (k in start..end) {
if (isFizz(k) && isBuzz(k))
println( "Fizz Buzz" )
else if (isFizz(k))
println( "Fizz" )
else if (isBuzz(k))
println( "Buzz" )
else
println(k)
}
}
|
Here, our if…else branches now invoke the nested functions isFizz and isBuzz. However, it is still a bit verbose to pass k to the function each time. Is there a way we can avoid this? Turns out, the answer is yes! We can define local functions not just directly inside other functions, but also in for loops, while loops, and other blocks:
Kotlin
fun fizzbuzz3(start: Int, end: Int): Unit {
for (k in start..end) {
fun isFizz(): Boolean = k % 3 == 0
fun isBuzz(): Boolean = k % 5 == 0
if (isFizz() && isBuzz())
println( "Fizz Buzz" )
else if (isFizz())
println( "Fizz" )
else if (isBuzz())
println( "Buzz" )
else
println(k)
}
}
|
In this third iteration of our function, we have moved the function definitions inside the for loop. So now, we can omit the parameter declarations and access k directly. Finally, we could take advantage of the Kotlin Basics, to remove some of the noise of the if…else keywords:
Kotlin
fun fizzbuzz4(start: Int, end: Int): Unit {
for (k in start..end) {
fun isFizz(): Boolean = k % 3 == 0
fun isBuzz(): Boolean = k % 5 == 0
when {
isFizz() && isBuzz() -> println( "Fizz Buzz" )
isFizz() -> println( "Fizz" )
isBuzz() -> println( "Buzz" )
else -> println(k)
}
}
}
|
This gives us our final solution, which avoids the repetition of code and is more readable than the initial iteration.
Similar Reads
Kotlin Inline Functions
In Kotlin, the higher-order functions or lambda expressions, all stored as an object so memory allocation, for both function objects and classes, and virtual calls might introduce runtime overhead. Sometimes we can eliminate the memory overhead by inlining the lambda expression. In order to reduce t
5 min read
Kotlin functions
In Kotlin, functions are used to encapsulate a piece of behavior that can be executed multiple times. Functions can accept input parameters, return values, and provide a way to encapsulate complex logic into reusable blocks of code. Learn Kotlin Functions What is Functions?Example of a FunctionType
8 min read
Kotlin - Scope Function
There are several functions in the Kotlin standard library that help in the execution of a block of code within the context of an object. Calling these functions on an object with lambda expression creates a temporary scope. These functions are called Scope Functions. We can access the object of the
7 min read
Function Literals with Receiver in Kotlin
In Kotlin, functions are first-class citizens. It means that functions can be assigned to the variables, passed as an argument, or returned from another function. While Kotlin is statically typed, to make it possible, functions need to have a type. It exists and it is called function type and these
3 min read
Introduction to Kotlin
Kotlin is a statically typed, general-purpose programming language developed by JetBrains, that has built world-class IDEs like IntelliJ IDEA, PhpStorm, Appcode, etc. It was first introduced by JetBrains in 2011 and a new language for the JVM. Kotlin is object-oriented language, and a "better langua
4 min read
Merge Two Collections in Kotlin
Kotlin is a statically typed, general-purpose programming language developed by JetBrains, that has built world-class IDEs like IntelliJ IDEA, PhpStorm, Appcode, etc. It was first introduced by JetBrains in 2011 and is a new language for the JVM. Kotlin is an object-oriented language, and a âbetter
4 min read
Kotlin unlabelled continue
In this article, we will learn how to use continue in kotlin. While working with loop in the programming, sometimes, it is desirable to skip the current iteration of the loop. In that case, we can use continue statement in the program. Basically, continue is used to repeat the loop for a specific co
3 min read
Idioms in Kotlin
Data classes in Kotlin are classes created to do nothing but hold data. Such classes are marked as data: data class User(var firstname: String, var lastname: String, var age: Int) The code above creates a User class with the following automatically generated: Getters and Setters for all properties (
4 min read
Launch vs Async in Kotlin Coroutines
The Kotlin team defines coroutines as âlightweight threadsâ. They are sort of tasks that the actual threads can execute. Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages. Kotlin coroutines introduce a new style of concurrency that can be used
4 min read
Delegation in Kotlin
Delegation controls the allocation of power/authority from an instance to another for any object. For classes and functions implementations, delegations can be used on static as well as mutable relations between them. Inheritance implementation in classes and functions can be altered with the help o
3 min read