Functional Programming
Functional Programming
Functional
Programming
We are already familiar with object-oriented programming (OOP), but Kotlin also
borrows concepts from functional programming (FP). FP is a programming
paradigm where programs are constructed by applying and composing
functions.
}
sum += item * item VS
}
Our approach
FP, like other concepts, has its advantages and disadvantages, but we will focus on its strengths.
Disclaimer: There won’t be any deep math or Haskell examples in this lecture. We will look at what
we consider to be the most important FP features that can be used in Kotlin
We already know that…
● If a function’s last argument is a function, then it can be put outside the parentheses:
fun baz(start: Int, end: Int, step: (Int) -> Unit): Unit { ... }
baz(23, 42) { println("Magnificent!") }
Functions that take other functions as arguments are called higher order functions.
Everything Kotlin allows you do with functions, which means that “functions in Kotlin are first-class
citizens.”
Higher order functions (HOFs)
In functional programming, functions are designed to be pure. In simple terms, this means they
cannot have a state. Loops have an iterator index, which is a state, so say goodbye to conventional
loops.
fun sumIter(term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double): Double {
fun iter(a: Double, acc: Double): Double = if (a > b) acc else iter(next(a), acc + term(a))
return iter(a, 0.0)
}
fun integral(f: (Double) -> Double, a: Double, b: Double, dx: Double): Double {
fun addDx(x: Double) = x + dx
return dx * sumIter(f, (a + (dx / 2.0)), ::addDx, b)
}
(This is a LISP program transcribed to Kotlin; nobody actually writes like this)
Higher order functions (HOFs)
Often in the context of FP it is necessary to operate with the following functions: map, filter, and fold.
Often in the context of FP it is necessary to operate with the following functions: map, filter, and fold.
x*x x+1
x x*x+1
x * x and x + 1
NB: to compose complex functions by default you can use sequences, but be careful.
Higher order functions (HOFs)
filter returns a list containing only elements that match a given predicate:
Our third important function, fold, creates a mutable accumulator, which is updated on each round
of the for and returns one value:
You can implement the fold function for any type, for example, you can fold a tree into a string
representation.
Higher order functions (HOFs)
There are also right and left folds. They are equivalent if the operation is associative: (a ○ b) ○ c = a ○ (b ○ c), but in any other
case they yield different results.
[One, one, was, a, race, horse, , Two, two, was, one, too, , One, one, won, one, race, , Two, two, won, one, too, , ]
Higher order functions (HOFs)
[One, one, was, a, race, horse, Two, two, was, one, too, One, one, won, one, race, Two, two, won, one, too]
Higher order functions (HOFs)
[one, one, was, a, race, horse, two, two, was, one, too, one, one, won, one, race, two, two, won, one,
too]
Higher order functions (HOFs)
[(one, 7), (was, 2), (a, 1), (race, 2), (horse, 1), (two, 4), (too, 2), (won, 2)]
Higher order functions (HOFs)
[(a, 1), (horse, 1), (was, 2), (race, 2), (too, 2), (won, 2), (two, 4), (one, 7)]
Higher order functions (HOFs)
val string = """
One-one was a race horse.
Two-two was one too.
One-one won one race. string
Two-two won one too. .allFunnyFuncs(...)
""".trimIndent() .toList()
.sortedWith { l, r ->
val result = string OR r.second - l.second
.split(" ", "-", ".", System.lineSeparator()) }
.filter { it.isNotEmpty() }
.map { it.lowercase() }
.groupingBy { it } string
.eachCount() .allFunnyFuncs(...)
.toList() .toList()
.sortedByDescending { (_, c) ->
.sortedBy { (_, count) -> count }
OR c
.reversed()
}
[(one, 7), (two, 4), (won, 2), (too, 2), (race, 2), (was, 2), (horse, 1), (a,
1)]
Higher order functions (HOFs)
Lambdas are not the only functions that can be passed as arguments to functions expecting other
functions, as references to already defined functions can be as well:
It will print just even into the console because of the lazy deferred computations.
Operator overloading
Kotlin has extension functions that you can use to override operators, for example the iterator. That
is, you do not need to create a new entity that inherits from the Iterable interface, as you would in
OOP code.
VS
class A<T>
operator fun <T> A<T>.iterator(): Iterator<T> = TODO("Not yet implemented")
One last thing…
Is this code correct? Yes, because the compiler knows all of the possible values.
What about this example? Once again, the answer is yes, because the compiler knows about all
possible children of the Color class at the compilation stage and no new classes can appear.
We have the common part in all classes and we know that these are the only possible subclasses.
Let’s move this code into the base class.
One last thing…
Actually, we have equivalent classes, i.e. each function for the first version can be rewritten as the
second one.
One last thing…
In the first function, we have smart casts, but in the second one we don't have them.
One last thing…
This is possible because we are actually operating with algebraic data types* and can use their
properties.
*This is not entirely true, but for most cases with sealed classes it works.
Final thought
FP in Kotlin does not kill OOP. Each of the concepts brings its own advantages and disadvantages,
and it is important to combine them in order to get concise, readable and understandable code!
If you are interested in the topic of FP in Kotlin for a more detailed study, come here:
https://arrow-kt.io/
Thanks!