Functional Programming with Kotlin and Arrow: Getting Started
In this tutorial, you will learn the fundamentals of functional programming and how various Kotlin language features enable functional programming concepts. By Massimo Carli.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Functional Programming with Kotlin and Arrow: Getting Started
30 mins
- Getting Started
- OOP in a Nutshell
- Types as Abstractions
- Abstracting Identity
- Collaboration
- Immutability
- Moving From Objects to Functions
- Defining Functions
- Function Types
- Applying Higher-Order Functions
- Functions in Kotlin: Considering Special Cases
- Special Case #1: A Function With Nothing
- Special Case #2: The Unit Function
- Special Case #3: Predicate Functions
- Function Composition
- Side Effects
- Pure Functions
- Logger: The FP Way
- Applying Abstraction
- Implementing Composition
- What About Arrow?
- Where to Go From Here?
Fundamentals are, of course, fundamental — and fun! This is particularly true for functional programming (FP). You may already use FP in your Kotlin code. If you’re curious about what’s behind this important paradigm, this is the tutorial for you.
This functional programming tutorial dives deeper into the following topics:
- Function types
- Side effects
- Higher-order functions
- Abstraction and composition
- The Arrow framework
Writing some code examples, you’ll learn how to “think functionally” and how FP can make your code more testable and safe. In order to go fast, we need to go well so let’s start!
Getting Started
First, click the Download Materials button at the top or the bottom of the page. You’ll see the initial and final code, but you’ll write most of it during this tutorial. Open the starter project in IntelliJ IDEA. In the Books.kt file, you’ll find the definition of the Book
and Price
classes:
data class Price(val value: Double, val currency: String = "$")
data class Book(
val ISDN: String,
val name: String,
val pages: Int,
val price: Price,
val weight: Double,
val year: Int,
val author: String
)
You’ll also find a list of instances used to initialize the books
property.
Just as Object Oriented Programming (OOP) means working with objects, Functional Programming (FP) means working with functions. This is the start of your journey from OOP to FP. Fasten your seatbelt!
OOP in a Nutshell
The main concept in OOP is the class because it’s the first construct that you can use to describe the makeup of objects — instances of that class — in terms of properties and operations.
To start, open Books.kt. Now, to create an instance of the Book
class, invoke one of its constructors and assign the returned reference to an androidBook
variable in main()
:
val androidBook = Book(
"8850333404",
"Android 6: guida per lo sviluppatore (Italian Edition)",
846,
Price(39.26, "£"),
2.1,
2016,
"Massimo Carli"
)
Note how Book
is not just a class but also defines a type. Book
is a type of androidBook
variable that can reference any other object of the same type. While an object is an instance of a specific class, its reference can be assigned to a variable of different types. Add this code at the end of main()
:
val obj: Any = androidBook
println("obj description: $obj")
This assignment is possible because a Book
is an extension of Any
. Stated another way, Any
is an abstraction of Book
.
Build and run. You should see the description for obj
in the Run window.
obj name: Book(ISDN=8850333404, name=Android 6: guida per lo sviluppatore (Italian Edition), pages=846, price=Price(value=39.26, currency=£), weight=2.1, year=2016, author=Massimo Carli)
Types as Abstractions
Abstraction is the most important concept in software development. An alternative name for abstraction is subtraction. In this context, abstracting means including only what’s necessary.
The variable androidBook
in the previous code is of the type Book
because you’re interested in its properties such as name
or price
. When you use the obj
variable of type Any
, it means that you don’t care about the book properties. You just care about the fact that it is an object.
Abstracting Identity
Abstraction makes one object equal to another. A lion isn’t a tiger, but they’re equal if you think of them as carnivorous animals. In the same way, think of a type of a variable as a way of representing all the possible values that the same variable can reference.
Add the following code to main()
:
val myBook: Book = androidBook
println("myBook description: $myBook")
Here myBook
is a Book
which means it can contain, or reference, any element in the set of all possible books. A type, then, is a way to represent a set of values. A class describes the makeup of all its instances, and the type abstracts the set of all of them.
Build and run. The descriptions for obj
and myBook
match as expected.
obj description: Book(ISDN=8850333404, name=Android 6: guida per lo sviluppatore (Italian Edition), pages=846, price=Price(value=39.26, currency=£), weight=2.1, year=2016, author=Massimo Carli)
myBook description: Book(ISDN=8850333404, name=Android 6: guida per lo sviluppatore (Italian Edition), pages=846, price=Price(value=39.26, currency=£), weight=2.1, year=2016, author=Massimo Carli)
Collaboration
In creating a program, you define how objects interact with each other. Generally, objects are instances of classes you design for them to collaborate.
One of the main benefits of OOP is encapsulation, in which objects interact using their interfaces, or a set of operations that other objects see and can invoke. Unfortunately, objects are not very good in a multithreaded environment because they encapsulate what really matters — the mutable state.
Objects hide the way they mutate their states and, because they collaborate, they share data. Mutable and shared states are the primary cause of data races, which often result in bugs.
Immutability
A data race happens when you have multiple threads accessing the same mutable state in an unsafe way. Even without FP, it’s possible to solve this problem by removing one of the causes.
A data race can’t exist if you have only one thread. Many systems use this Single Thread Model for UI management, allowing a single main thread — in this case, the UI thread — to access UI components.
Another option is not to allow mutable states. This is the case when you create immutable objects, which are thread-safe because they can’t change their state after creation.
Using immutable objects is a good practice in both OOP and FP. Your Book
class is already immutable because of val instead of var in the definition of its properties.
To provide a mutable version of the same class, make it explicit defining the MutableBook class. Add the following code to Books.kt:
data class MutableBook(
var ISDN: String,
var name: String,
var pages: Int,
var price: Price,
var weight: Double,
var year: Int,
var author: String
)
Moving From Objects to Functions
You might have some key questions about moving from objects to functions:
- What’s the equivalent of a class in an FP world?
- What’s the equivalent of an instance?
- Can you use the same logical path you used with classes and objects?
- What about types, abstraction and collaboration? Can a function be the solution to the data race problem you face in OOP?
Read on for the answers to these questions!