
This is a chapter from the book Functional Kotlin. You can find it on LeanPub or Amazon. It is also available as a course.
This part was written by Alejandro Serrano Mena, with support from Simon Vergauwen and Raúl Raja Martínez.
io.arrow-kt:arrow-core as a dependency in your project. At the time of writing, the library is in the 1.1.x series, with 2.0 being planned and worked on.arrow.core package. The first one is compose, which creates a new function by executing two functions, one after another:val squaredPlusOne: (Int) -> Int = { x: Int -> x * 2 } compose { it + 1 }
{ x: Int -> (x + 1) * 2 }. The composition of functions works from right to left. This is often surprising at first because we read code from left to right. However, this can simplify complex chains of functions, especially when using function references, whereas the corresponding version with explicit parameters requires nesting.people.filter( Boolean::not compose ::goodString compose Person::name ) // instead of people.filter { !goodString(it.name) }
goodString with a different check. In particular, we want to check whether the string starts with a given prefix. To do so, we want to use the isPrefixOf function, which takes such a prefix as an argument.fun String.isPrefixOf(s: String) = s.startsWith(this)
::goodString with String::isPrefixOf, the compiler rightly complains. It's expecting a function with a single argument, but isPrefixOf has two (the receiver and the argument s). We could create a lambda that gives the first argument, but another solution is to use one of the helper functions in Arrow Core.(String::isPrefixOf).partially1("FP")
isPrefixOf. You may have noticed the 1 at the end of partially1. Arrow Core includes functions to partially apply not only one but up to 22 arguments at once.DeepRecursiveFunction constructor to define it:val fibonacci = DeepRecursiveFunction<Int, Int> { x -> when { x < 0 -> 0 x == 1 -> 1 else -> callRecursive(x - 1) + callRecursive(x - 2) } }
fun but as a val, so we prefer to have an actual bridge function that starts the recursive computation.fun fib(x: Int) = fibonacci(x)
fib(4), which requires both fib(3) and fib(2). But the computation of fib(3) also requires fib(2)! Since this function is pure, we know that both calls to fib(2) return the same value. For these scenarios, we can apply the technique of memoization, i.e., caching intermediate values to avoid recomputations. Arrow Core contains a specific function called memoize, which takes care of creating and updating this cache, so all we need to do is:fun fibM(x: Int) = ::fib.memoize()(x)
fib(2), the entire sequence at that point has been computed and cached. We go from an exponential blowup to a linear function.Int: values close to zero and close to the overflow and underflow points tend to break functions that don't account for these corner cases. Kotest comes with a big battery of generators inthe Arb object, but there's no support for generating functions. This means you cannot test higher-order functions, so you generally need to resort to good ol' unit testing in these cases; however, if you bring the
kotest-property-arrow library into your project, this limitation is gone.val gen = Arb.functionAToB<Int, Int>(Arb.int())
map.