Functional Programming With Kotlin and Arrow — Algebraic Data Types
Learn how to use algebraic operations to better understand functional programming concepts like class constructs, typeclasses and lists in Kotlin & Arrow. 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 — Algebraic Data Types
35 mins
- Getting Started
- What Is Algebra?
- Data Types and Multiplication
- Multiplying the Unit Type
- Multiplying the Nothing Type
- Multiplying Classes
- Data Types and Addition
- Understanding the Unit and Nothing Types
- Putting Algebra to Work
- Using Algebra for Type Safety
- Other Algebraic Properties
- Using Algebra With the Optional Type
- More Fun With Exponents
- Understanding Currying
- Carrying and the Flip Function
- Using Algebra With the List Type
- Functional Lists and Algebra
- Where to Go From Here?
Functional programming is magic, and in this tutorial, you’ll use simple algebraic operations to get a deeper understanding of the most important functional programming principles.
In the previous tutorial, Functional Programming with Kotlin and Arrow Part 4 – Generate Typeclasses With Arrow, you learned how to generate the code to implement important typeclasses like Functor, Applicative and Monad.
In this tutorial, you’ll build on that knowledge to learn:
- What algebra is and how it translates to the class construct and the
Either<E, T>
typeclass in Kotlin. - How and when algebraic data types are useful, including a common practical example.
- After addition and multiplication, you’ll see what the implications of exponents are.
- What a simple List<T> has in common with algebra.
Understanding algebraic datatypes and how to use them will help you to master functional programming as they are very useful for encoding the business logic in applications.
Time to do some coding magic! :]
Getting Started
Download the materials for this tutorial using the Download Materials button at the top or bottom of this page. Open the project using IntelliJ 2019.x or greater and you’ll see the following structure:
All the files are initially empty; it’ll be your job to add the code throughout this tutorial. But before writing any code, it’s important to understand the concept of algebra.
What Is Algebra?
Algebra is a generalization of arithmetic that lets you combine numbers and letters representing numbers by using specific rules. Here’s an example of a simple algebraic expression:
a * X ^ 2 - b * X + c
In this example, you have numbers like the 2
, letters like a
, b
and c
and operations like multiplication *
, addition +
and exponentiation ^
.
Algebra is the set of rules that allow you to combine all those different symbols. But what does this have to do with Kotlin and functional programming?
Algebra and functional programming has a lot in common and programmers can use algebra to understand exactly how functional programming constructs work, starting with product types.
Data Types and Multiplication
The Kotlin APIs define many classes, including Pair<A, B>
, which has the following simple code:
public data class Pair<out A, out B>(
public val first: A,
public val second: B
) : Serializable {
// ...
}
This class consists of a simple pair of values, the first of type A
and the second of type B
.
In the first tutorial of the series, Functional Programming with Kotlin and Arrow: Getting Started, you saw that a type is a way to represent all the possible values that a variable of that type can assume. For instance, a variable of type Boolean can contain the value true
or false
.
What about the Pair<A, B>
type? How many values are available for a variable of type Pair<A, B>
? To understand this, consider the type you’re defining by copying the following code into Struct.kt:
typealias BoolPair = Pair<Boolean, Boolean>
If you want to count all the possible values for a variable of type BoolPair
, simply add the following code:
val bool1 = Pair(true, true)
val bool2 = Pair(true, false)
val bool3 = Pair(false, true)
val bool4 = Pair(false, false)
From a pair of Boolean
variables, which you can consider as the value 2
, you get 4
values in total. But do you get those four values by adding 2+2
or multiplying 2*2
?
Answer this question by adding the following definition to the same file:
enum class Triage {
RED, YELLOW, GREEN
}
This defines the Triage
type, which is an enum
with three different values: RED
, YELLOW
and GREEN
. Next, add the following code:
typealias BoolTriage = Pair<Boolean, Triage>
This defines a Pair
consisting of a Boolean
and a Triage
. Now, repeat the same question: How many values does this type have?
To find out, simply use the following code:
val triple1 = Pair(true, Triage.RED)
val triple2 = Pair(true, Triage.YELLOW)
val triple3 = Pair(true, Triage.GREEN)
val triple4 = Pair(false, Triage.RED)
val triple5 = Pair(false, Triage.YELLOW)
val triple6 = Pair(false, Triage.GREEN)
which proves that the possible values are:
Boolean * Triage = 2 * 3 = 6
This brings you to the conclusion that a Pair<A, B>
has as many values as the product of multiplying the values of A
by the values of B
.
Now take a look at what happens when incorporating the Unit type into the multiplication.
Multiplying the Unit Type
In the first tutorial of this series, you learned that the Unit
type has a single instance with the same name Unit
. In Struct.kt, add the following definition:
typealias UnitTriage = Pair<Unit, Triage>
Now, note that the number of possible values is the value you get by adding the following code to the same file:
val unit1 = Pair(Unit, Triage.RED)
val unit2 = Pair(Unit, Triage.YELLOW)
val unit3 = Pair(Unit, Triage.GREEN)
You then have:
Unit * Triage = 1 * 3 = 3
This proves that the Unit is equivalent to the value 1
when you multiply with it.
Multiplying the Nothing Type
In the first tutorial of this series, you also learned about the Nothing
type, which is a type with no values. What happens if you add the following definition to Struct.kt?
typealias NothingTriage = Pair<Nothing, Triage>
When you try to add the following code, you’ll get an error. This is because you can’t have a value of type Nothing
so you can’t create an instance of the NothingTriage
type.
val nothing1 : NothingTriage = Pair(???, Triage.RED)
This means that the type Nothing
corresponds to the value 0 for multiplication purposes. In this case you can say that:
Nothing * Triage = 0 * 3 = 0
Multiplying Classes
In the previous examples, you used Pair<A, B>
but what happens if you define a class, like so:
data class Struct(val enabled: Boolean, val triage: Triage, val value: Byte)
Based on what you learned in this paragraph, you can say that:
Struct = Boolean * Triage * Byte = 2 * 3 * 256 = 1536
The number of possible values is the product of multiplying all the values of the aggregated types. In this last example, Byte
has 256 values, so the total number of values is 1,536.
But what happens when you do something like this instead:
data class Struct2(val enabled: Boolean, val triage: Triage, val name: String)
String
has many possible values, so you can’t determine an exact result — but having an exact result is also not important. As you’ll see later, the important thing is to understand that you can represent the relationship between types as a multiplication operation.