Functional programming revolves around the concepts of immutability and functions.
Functions in Kotlin
A function is a block of organized, reusable code that is used to perform a single, related action.
Following is the functionality of a function:
In Kotlin, a function generally looks like the following:
fun functionName(parameter1:DataType1, parameter2:DataType2,...): ReturnType { //do your stuff here return returnTypeObject }
Returning two values from a function
While, generally, a function can return only a single value, in Kotlin, by leveraging the benefits of the Pair
type and destructuring declarations, we can return two variables from a function. Consider the following example:
fun getUser():Pair<Int,String> { return Pair(1,"Rivu") } fun main(args: Array<String>) { val (userID,userName) = getUser() println("User ID: $userID t User Name: $userName") }
In the preceding program, we created a function that would return a Pair<Int,String> value. We used that function in a way that seems like it returns two variables. Actually, destructuring declarations allows you to destructure a data class/Pair and get its underlying values in standalone variables. When this feature is used with functions, it seems like the function is returning multiple values, though it returns only one value that is a Pair
value or another data class.
Lambda
Lambda or lambda expressions generally means anonymous functions, that is, functions without names, which can be assigned to variables, passed as arguments, or returned from another function. It is a kind of nested function, but is more versatile and more flexible.
While, in Java, lambda is only supported starting with Java 8, in Kotlin, you can use Kotlin with JVM 6 onwards, so there's really no barrier for lambda in Kotlin.
Let's have a look at the following code:
fun unless(condition: Boolean, block: () -> Unit) { if (!condition) block() } unless(someBoolean) { println("You can't access this website") }
Single-expression functions
The function sum
takes two Int
values and adds them. Declared in a normal way, we must provide a body with curly braces and an explicit return:
fun sum(a:Int, b:Int): Int { return a + b }
Our sum
function has its body declared inside curly braces with a return clause. But if our function is just one expression, it could have been written in a single line:
fun sum(a:Int, b:Int): Int = a + b
So, no curly braces, no return clause, and an equals (=) symbol. If you pay attention, it just looks similar to a lambda. If you want to cut even more characters, you can use type inference too:
fun sum(a:Int, b:Int) = a + b
Use type inference for a function's return when it is very evident which type you are trying to return. A good rule of thumb is to use it for simple types such as numeric values, Boolean, string, and simple data class constructors. Anything more complicated, especially if the function does any transformation, should have explicit types.
Function as property
Kotlin also allows us to have functions as properties. Functions as properties means that a function can be used as a property. For instance, take the following example:
fun main(args: Array<String>) { val sum = { x: Int, y: Int -> x + y } println("Sum ${sum(10,13)}") println("Sum ${sum(50,68)}") }
In the preceding program, we created a property, sum
, which will actually hold a function to add two numbers passed to it.
While sum
is a val
property, what it holds is a function (or lambda) and we can call that function just like the usual function we call; there are no differences there at all.
Pure functions
The definition of a pure function says that, if the return value of a function is completely dependent on its arguments/parameters, then this function may be referred to as a pure function. So, if we declare a function as fun func1(x:Int):Int
, then its return value will be strictly dependent on its argument, x
; say, if you call func1
with a value of 3 N times, then, for every call, its return value will be the same.
The definition also says that a pure function should not actively or passively cause side effects, that is, it should not directly cause side effects, nor should it call any other function that causes side effects.
A pure function can be either a lambda or a named function.
So, why are they called pure functions? The reason is quite simple. Programming functions originated from mathematical functions. Programming functions, over time, evolved to contain multiple tasks and perform anonymous actions that are not directly related to the processing of passed arguments. So, those functions that still resemble mathematical functions are called pure functions.
So, let's modify our previous program to make it into a pure function:
fun addNumbers(a:Int = 0,b:Int = 0):Int { return a+b } fun main(args: Array<String>) { println(addNumber(2,2)) }
Extension functions
Extension functions let you modify existing types with new functions:
fun String.sendToConsole() = println(this) fun main(args: Array<String>) { "Hello world! (from an extension function)".sendToConsole() }
To add an extension function to an existing type, you must write the function's name next to the type's name, joined by a dot (.).
In our example, we add an extension function (sendToConsole()
) to the String
type. Inside the function's body, this refers the instance of String
type (in this extension function, string is the receiver type).
Apart from the dot (.) and this, extension functions have the same syntax rules and features as a normal function. Indeed, behind the scenes, an extension function is a normal function whose first parameter is a value of the receiver type. So, our sendToConsole()
extension function is equivalent to the next code:
fun sendToConsole(string: String) = println(string) sendToConsole("Hello world! (from a normal function)")
So, in reality, we aren't modifying a type with new functions. Extension functions are a very elegant way to write utility functions, easy to write, very fun to use, and nice to read. This also means that extension functions have one restriction - they can't access private members of this, in contrast with a proper member function that can access everything inside the instance:
class Human(private val name: String) //Cannot access 'name': it is private in 'Human' fun Human.speak(): String = "${this.name} makes a noise"
Invoking an extension function is the same as a normal function - with an instance of the receiver type (that will be referenced as this inside the extension), invoke the function by name.
Infix functions
Functions (normal or extension) with just one parameter can be marked as infix
and used with the infix notation. The infix notation is useful to express the code naturally for some domains, for example, math and algebra operations.
Let's add an infix extension function to the Int
type, superOperation
(which is just a regular sum with a fancy name):
infix fun Int.superOperation(i: Int) = this + i fun main(args: Array<String>) { 1 superOperation 2 1.superOperation(2) }
We can use the superOperation
function with the infix
notation or normal notation.
Operator overloading
Operator overloading is a form of polymorphism. Some operators change behaviors on different types. The classic example is the operator plus (+). On numeric values, plus is a sum operation and on String is a concatenation. Operator overloading is a useful tool to provide your API with a natural surface. Let's say that we're writing a Time and Date library; it'll be natural to have the plus and minus operators defined on time units.
Kotlin lets you define the behavior of operators on your own or existing types with functions, normal or extension, marked with the operator modifier:
class Wolf(val name:String) { operator fun plus(wolf: Wolf) = Pack(mapOf(name to this, wolf.name to wolf)) } class Pack(val members:Map<String, Wolf>) fun main(args: Array<String>) { val talbot = Wolf("Talbot") val northPack: Pack = talbot + Wolf("Big Bertha") // talbot.plus(Wolf("...")) }
The operator function plus
returns a Pack
value. To invoke it, you can use the infix
operator way (Wolf + Wolf
) or the normal way (Wolf.plus(Wolf)
).
Something to be aware of about operator overloading in Kotlin - the operators that you can override in Kotlin are limited; you can't create arbitrary operators.
Inline functions
High-order functions are very useful and fancy, but they come with a caveat - performance penalties. On compilation time, a lambda gets translated into an object that is allocated, and we are calling its invoke
operator; those operations consume CPU power and memory, regardless of how small they are.
A function like this:
fun <T> time(body: () -> T): Pair<T, Long> { val startTime = System.nanoTime() val v = body() val endTime = System.nanoTime() return v to endTime - startTime } fun main(args: Array<String>) { val (_,time) = time { Thread.sleep(1000) } println("time = $time") }
Once compiled, it will look like this:
val (_, time) = time(object : Function0<Unit> { override fun invoke() { Thread.sleep(1000) } })
If performance is a priority for you (mission critical application, games, video streaming), you can mark a high-order function as inline
:
inline fun <T> inTime(body: () -> T): Pair<T, Long> { val startTime = System.nanoTime() val v = body() val endTime = System.nanoTime() return v to endTime - startTime } fun main(args: Array<String>) { val (_, inTime) = inTime { Thread.sleep(1000) } println("inTime = $inTime") }
Once compiled, it will look like this:
val startTime = System.nanoTime() val v = Thread.sleep(1000) val endTime = System.nanoTime() val (_, inTime) = (v to endTime - startTime)
The whole function execution is replaced by the high-order function's body and the lambda's body. The inline
functions are faster, albeit generating more bytecode.
So, inline functions are used to save us memory overhead by preventing object allocations for the anonymous functions/lambda expressions called. Instead, it provides that functions body to the function that calls it at runtime. This increases the bytecode size slightly but saves us a lot of memory.