Reader small image

You're reading from  Kotlin Design Patterns and Best Practices - Second Edition

Product typeBook
Published inJan 2022
Reading LevelBeginner
PublisherPackt
ISBN-139781801815727
Edition2nd Edition
Languages
Right arrow
Author (1)
Alexey Soshin
Alexey Soshin
author image
Alexey Soshin

Alexey Soshin is a software architect with 15 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design.
Read more about Alexey Soshin

Right arrow

Chapter 5: Introducing Functional Programming

This chapter will discuss the fundamental principles of functional programming and how they fit into the Kotlin programming language.

As you'll discover, we've already touched on some of the concepts in this chapter, as it would have been hard to discuss the benefits of the language up until now without touching on functional programming concepts such as data immutability and functions as values. But as we did before, we'll look at those features from a different angle.

In this chapter, we will cover the following topics:

  • Reasoning behind the functional approach
  • Immutability
  • Functions as values
  • Expressions, not statements
  • Recursion

After completing this chapter, you'll understand how the concepts of functional programming are embedded in the Kotlin language and when to use them.

Technical requirements

For this chapter, you will need to install the following:

You can find the code files for this chapter on GitHub at https://github.com/PacktPublishing/Kotlin-Design-Patterns-and-Best-Practices/tree/main/Chapter05.

Reasoning behind the functional approach

Functional programming has been around for as long as other programming paradigms, for example, procedural and object-oriented programming. But in the past 15 years, it has gained significant momentum. The reason for this is that something else stalled: CPU speeds. We cannot speed up our CPUs as much as we did in the past, so we must parallelize our programs. And it turns out that the functional programming paradigm is exceptionally good at running parallel tasks.

The evolution of multicore processors is a fascinating topic in itself, but we'll cover it only briefly here. Workstations have had multiple processors since at least the 1980s to support the running of tasks from different users in parallel. Since workstations were massive during this era, they didn't need to worry about cramming everything into one chip. But when multiprocessors came to the consumer market around 2005, it became necessary to have one physical unit that...

Immutability

One of the fundamental concepts of functional programming is immutability. This means that from the moment the function receives input to the moment the function returns output, the object doesn't change. But how could it change? Well, let's look at a simple example:

fun <T> printAndClear(list: MutableList<T>) {
    for (e in list) {
        println(e)
        list.remove(e)
    }
}
printAndClear(mutableListOf("a", "b", "c"))

This code would output a first, and then we would receive ConcurrentModificationException.

The reason for this is that the for-each loop uses an iterator (which we already discussed in the previous chapter), and by mutating the list inside the loop, we interfere with its operation. However, this raises a question:

Wouldn't it be great if we could protect ourselves...

Functions as values

We already covered some of the functional capabilities of Kotlin in the chapters dedicated to design patterns. The Strategy and Command design patterns are only two examples that rely heavily on the ability to accept functions as arguments, return functions, store functions as values, or put functions inside of collections. In this section, we'll cover some other aspects of functional programming in Kotlin, such as function purity and currying.

Learning about higher-order functions

As we discussed previously, in Kotlin, it's possible for a function to return another function. Let's look at the following simple function to understand this syntax in depth:

fun generateMultiply(): (Int) -> Int {
    return fun(x: Int): Int {
        return x * 2
    }
}

Here, our generateMultiply function returns another function that doesn't have a name. Functions without...

The it notation

It is very common in functional programming to keep your functions small and simple. The simpler the function, the easier it is to understand, and the more chances it has to be reused in other places. And the aim of reusing code is one of the basic Kotlin principles.

Notice that in the preceding example, we didn't specify the type of the d variable. We could do this using the same colon notation we have used elsewhere:

dwarfs.forEach { d: String ->  
    println(d) 
}

However, usually, we don't need to do this because the compiler can figure this out from the generic types that we use. After all, dwarfs is of the List<String> type, so d is of the String type as well.

The type of the argument is not the only part that we can omit when writing short lambdas like this one. If a lambda takes a single argument, we can use the implicit name for it, which in this case, is it:

dwarfs.forEach {
   ...

Using expressions instead of statements

A statement is a block of code that doesn't return anything. An expression, on the other hand, returns a new value. Since statements produce no results, the only way for them to be useful is to mutate the state, whether that's changing a variable, changing a data structure, or performing some kind of IO.

Functional programming tries to avoid mutating the state as much as possible. Theoretically, the more we rely on expressions, the more our functions will be pure, with all the benefits of functional purity.

We've used the if expression many times already, so one of its benefits should be clear: it's less verbose and, for that reason, less error-prone than the if statement from other languages.

Pattern matching

The concept of pattern matching will seem like switch/case on steroids. We've already seen how the when expression can be used, which we explored in Chapter 1, Getting Started with Kotlin, so let&apos...

Recursion

Recursion is a function invoking itself with new arguments. Many well-known algorithms, such as Depth First Search, rely on recursion.

Here is an example of a very inefficient function that uses recursion to calculate the sum of all the numbers in a given list:

fun sumRec(i: Int, sum: Long, numbers: List<Int>): Long {
    return if (i == numbers.size) {
        return sum
    } else {
        sumRec(i+1, numbers[i] + sum, numbers)
    }
}

We often try to avoid recursion due to the stack overflow errors that we may receive if our call stack is too deep. You can call this function with a list that contains a million numbers to demonstrate this:

val numbers = List(1_000_000) {it}
println(sumRec(0,  numbers)) 
// Crashed pretty soon, around 7K

However, Kotlin supports an optimization called tail recursion. One...

Summary

You should now have a better understanding of functional programming and its benefits, as well as how Kotlin approaches this topic. We've discussed the concepts of immutability and pure functions, and how combining these results in more testable code that is easier to maintain.

We discussed how Kotlin supports closures, which allow a function to access the variables of the function that wraps it and effectively store the state between executions. This enables techniques such as currying and memoization that allow us to fix some of the function arguments (by acting as defaults) and remember the value returned from a function in order to avoid recalculating it.

We learned that Kotlin uses the tailrec keyword to allow the compiler to optimize tail recursion. We also looked at higher-order functions, expressions versus statements, and pattern matching. All of these concepts allow us to write code that is easier to test and has less risk of concurrency bugs.

In...

Questions

  1. What are higher-order functions?
  2. What is the tailrec keyword in Kotlin?
  3. What are pure functions?
lock icon
The rest of the chapter is locked
You have been reading a chapter from
Kotlin Design Patterns and Best Practices - Second Edition
Published in: Jan 2022Publisher: PacktISBN-13: 9781801815727
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Alexey Soshin

Alexey Soshin is a software architect with 15 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design.
Read more about Alexey Soshin