Basics of Functional Programming with Kotlin (1 of 3)

So you're interested in functional programming and want to learn it, great. In case you are still confused about whether to use/learn functional programming or not and what benefits it'll serve to you / your performance overall, then let's get it sorted and get rid of your confusion first.

Why Functional Programming?

Every developer like me, who are from OOP background gets a FOMO (Fear to Move Out) to start on functional programming.
If we want to describe the benefits of functional programming in one sentence then it'll say "Functional Programming helps you write pragmatic, less error-prone and concurrency ready (without any extra effort) code".
How? Let us start answering this by knowing what functional programming is.

What is Functional Programming

Functional programming is a programming paradigm in which we try to bind everything in pure mathematical functions style.
Credit - https://www.geeksforgeeks.org/functional-programming-paradigm/

Many people say functional programming is a replacement of Object Oriented Programming and they're very different. However, in my opinion, you can think of functional programming not as a replacement but as an evolution of OOP.
Both FP and OOP rely on Abstraction and Polymorphism however instead of Inheritance in OOP, FP puts its bet on Composition (explained later in the series). Another big difference between OOP and FP is that in OOP we rely mostly on classes and properties of those classes, however, in functional programming having properties in classes other than data (POJO) classes (or in simpler words having global variables) is strongly discouraged, what is done instead is, we use more Pure Functions and Immutability and pass data from one function to another.
The objective here is to stay as close to mathematics as possible.
So, if you can consider functions similar to an immutable hashmap, where a particular parameter always makes the function return the same value and avoid state change, then the concurrency problem is automatically solved.

Even if you don't want to follow FP, you can still use the four concepts below to make your program scalable.

FP Concepts

Still here? That means you're now determined to learn Functional Programming, so let's get started.
Every programming paradigm stands on top of few basic concepts, for example, to learn Object Oriented Programming (OOP), you've to learn basic concepts underlying it such as Inheritance, Polymorphism, Abstraction etc. Similarly, to learn Functional Programming, you first need to learn four basic concepts of functional programming as listed below, please note that even if you don't want to follow FP, you can still use the four concepts below to make your program scalable.

  • Immutability
  • Pure Functions
  • Lambda
  • High-Order Functions

So lets with them.

Immutability

The concept of Immutability says, that a variable should return the exact same value when accessed at different times. This might sound similar to Constants, they are in fact similar to some extent, but it's not entirely the same.
Let us take a look at below code to understand.

data class MyData(val someValue1: String, val someValue2: String)

fun processUpdatedData(myData: MyData) {
    ....
}

val myData = MyData("V1", "V2")
processUpdatedData(myData.copy(
    someValue2 = "Updated value"
))

In the above example, myData is an immutable variable, but when we need to use an updated value, we are using the copy constructor to get an updated instance of the MyData class, where the actual variable myData still holds the same value.
In Java, the String class is immutable, but you can still modify a String variable, what is done here is that whenever you modify a String variable in Java, it internally uses the copy constructor, sets the updated value in a new instance of String, and updates the register (internal pointer) for your variable to point to the new location in memory where the updated instance resides. While the old value still resides in memory at its old place. Now, if you're thinking of memory efficiency, then let me tell you java did that to improve memory efficiency only. So every String variable in Java, which has the exact same value, resides in the same memory location, if you modify one variable among them, that particular variable will just start pointing to some other memory location with the updated value.
So, that was about the concept of immutability and how Java used immutability to increase memory efficiency, how to implement them in your code in Kotlin? Let's find out.
Unlike Java, Kotlin provides you easy ways to use immutability, and unlike other functional languages, Kotlin doesn't enforce immutability, but it encourages you to use Immutability.
For your variable, you can use val instead of var, and please don't use any hacks to change the value of the val variable as you like, val just works like final variable in Java, it doesn't guaranty immutability, it's solely on the developers hand whether to respect immutability or not, as Kotlin uses getters and setters for variable access, a developer can always bypass immutability by using a custom getter, and please don't do that, please use a var instead of you really need such operations.
Kotlin also provides support for immutable collections, in fact by default all collection objects are immutable (like List, Map, Set etc.), if you need to modify them after creation, you can use the mutable variant of these collections (eg. MutableList, MutableSet, MutableMap etc.).

Pure Functions

The idea behind Pure Functions is that functions with their parameter should be cache-able, in order to get better performance, to achieve this, functions need to resemble their mathematical counterpart, that is, a function f should return the exact same value y when x is passed as parameter, in mathematical notation f(x) = y should always be true. Another important thing is a function shouldn't use/modify anything outside its scope.
Let's take an example.

fun power(number: Int, exponent: Int): Int {
    var result = 1
    if (exponent < 0) {
        throw Exception("Negative value of exponent isn't supported, passed $exponent")
    }
    for(i in 1..exponent) {
        result = result * number
    }
    return result
}

In the above program, function power() would always return the same value for a given combination of number and exponent also, this function doesn't access or modify anything out of it's scope thus the power() is a Pure Function.

Side Effects

When a function (presumed Pure Function) accesses or modifies anything out of it's scope, then that function is considered having Side Effects.
For example if we add a log / println in the above function (the power() function), then it'd not be Pure anymore and would be considered having Side Effects, and that log/println would be the said Side Effect.

One important point to note here is that the `throw` statement that we used above is also a side-effect, how to remove that? We will be learning in the next post of this series with Arrow.

Lambda

Lambda are often referred as anonymous functions, Lambda (uppercase Λ, lowercase λ) is actually 11th letter in Greek alphabet and often used in mathematics to denote functions. That's where the term lambda came from into programming.
So, what are they exactly? With lambda you can write a function like a mathematical expression, using a pair of curly braces and an arrow notation (->).
Kotlin being a Functional Language, and treating Functions as it's first class citizen, gives you freedom to assign a lambda to a variable and even pass them to another function (that's where the High-Order Function comes into picture, discussed in the next section).
Let's consider the below example.

val power = {number: Int, exponent: Int ->
    var result = 1
    if (exponent < 0) {
        throw Exception("Negative value of exponent isn't supported, passed $exponent")
    }
    for(i in 1..exponent) {
        result = result * number
    }
    result
}

println(power(10,2))
println(power.invoke(10,3))

So, in the above example, the power is a variable as well as a function at a time, or it's better to say, that the power is a variable, which holds a function. The lambda, started and ended with the pair of curly braces, inside that, before the arrow notation (->), we provided the name and types of for the parameters, and the function body after ->.
The lambda returns the value last line, in this case it'll return the value of result.
A lambda function can be invoked like an usual function or also by calling invoke().
To denote a variable would contain a lambda, you can use (PARAMETERS)->RETURN_TYPE, replacing PARAMETERS with list of parameters, and RETURN_TYPE with the return type of the function/lambda.
So, as we learned a bit about Lambda, let's move towards it's close relative - High-Order Function.

High-Order Functions

The name High-Order Function implies that these are functions which have higher order / higher class, than the usual functions. This is in fact to some extent correct. Those functions, which take other functions as parameter or returns another function are called High-Order Function, and here's where lies a strong coupling of Lambda and High-Order Function.
Basically, High-Order Functions take Lambda(s) as parameter, or return a Lambda.
Below are two examples of High-Order Functions.

fun applyToNumberIfPositive(number: Int, lambda: (Int)->Int): Int {
    return if(number>0) lambda(number) else number
}

fun getSquareFunction(): (Int)->Int {
    return {number ->
        number*number
    }
}

The first one is a high-order function, which expects a lambda as parameter, whereas the second one is a high-order function which returns a high-order function.
We can invoke them as follows.

applyToNumberIfPositive(5, {
    number ->
    number*2
})
getSquareFunction().invoke(2)
getSquareFunction()(4)
//We can also invoke them together.
applyToNumberIfPositive(10, getSquareFunction())

We could invoke both the High-Order functions together, as the applyToNumberIfPositive function expects a lambda with signature exactly same as the getSquareFunction function returns.
Cool, isn't it?

Wrapping Up

So, in this post, we learned about basics of Functional Programming and it's underlying concepts, i.e. Immutability, Pure Functions, Side Effects, Lambda and High-Order Functions.
Most important of all, we crossed FOMO to finally begin learning Functional Programming.
But these are just beginning, to properly learn Functional Programming, one must also learn Typed FP and Category Theory, which are covered in the next post of this series.

In the next article of this series, we will discuss Typed FP and Category Theory and will see how to use Arrow for Type FP in Kotlin