Bounded Functional Loops in Kotlin

Loops are a basic paradigm in imperative programming languages. In functional languages you also need to loop, but you do it differently. Here, I present how I prefer to implement loops in a functional style using Kotlin. To check, if this is a good idea at all, I do some benchmarks against imperative variants and good old Haskell.

When I have some spare time left, I like to play with new things. For some months now, I invest this time to learn Kotlin. Because there is not enough time for a pet project, I solve small exercises on CodeWars. To make it even more motivating I decided to implement all solutions in a purely functional way without any side effects. This is actually fun, even though Kotlin itself is far away from being a pure functional programming language. The main approach in Kotlin for computations often consist of typical imperative for-loops. So let us see how to do them in a functional style.

Finite Loops on containers

A long time ago, as a student I learned that there are two types of loops in programming languages. The first type is the typical for-loop with fixed boundaries. These are known as LOOP programs or primitive recursive functions. These have the nice property that they always terminate within an upper bound of time. Here is an example:

data class OrderItem (val orderNumber: String, val amount: Int )

fun sumImperativ(items : List<OrderItem>) : Int {
    var result = 0
    for( i in 0 until items.size) {
        result += items[i].amount
    }
    return result
}

An OrderItem consist of some order number and an amount of ordered items. The function sumImperativ loops through a list of order items, extract the amounts, and sums them up. The for-loop is fixed. If items is not changed in the loop, then the maximum number of iterations is items.size. And, because the interface List does not contain any write method in Kotlin, items cannot be changed.

There are still some issues with the solution. It is easily possible to get the index wrong. If, for example, the loop would be for(i in 0 ..items.size) you would access i[items.size] which results in an ArrayIndexOutOfBoundsException. The mutable variable result is used to sum up alle the values. In this short example this may work. But in more complex scenarios, errors may sneak into the code.

The functional community often claims that using functional programming (fp), the resulting code is much less error prone. So let us have a look, how this simple loop is done in Haskell, the old champion in the fp programming languages league:

sumOrderItems items = summedAmount where
  amounts = map amount items
  summedAmount = sum amounts

First you extract all the amounts of the item by mapping the extractor function amount on the list of order items. As result you get amounts as a list of integers and just sums them up. In this short example you would probably inline the variables:

sumOrderItems2 items = sum (map amount items)

Now we get rid of index problems and split the computation in the loop cleanly in its two parts, extraction and summation. There are no side effects and much less things can get wrong. But, looking at the loops we are somehow cheating. This is, because the loops are hidden behind the utility functions map and sum. Their implementation may be hard, especially if the container data types are complex. But because of the functional design the application developer does not need to care. This has proven so valuable that more or less every modern programming language supports this approach. This is the implementation in Kotlin:

fun sumFunctional(items: List<OrderItem>): Int =
        items.map(OrderItem::amount)
             .sum()

This looks pretty similar to Haskell. IMHO if you use this style of development in Kotlin, the thinking style is pretty similar to the development using Haskell.

Finite Loops without Containers

Very often, looping is done over the contents of container, but not always. A common use case is a mathematical computation. Let us look at a textbook example, the integration of a function. For many function types, an integration can be approximated by transforming the function into a sequence of rectangles, and then sum up the area of the rectangles. Using a finite loop, this is how the algorithm can look like in Kotlin:

fun integrateImperative(start :Double, end : Double, precision: Long,
                        f : (Double) -> Double) : Double {
    val step = (end-start) / precision
    var result = 0.0
    var x = start
    for ( i in 0 until precision) {
        result += f(x) * step
        x += step
    }
    return result
}

Even though this loop is not very complicated, I usually need some time to clearly understand what happens here. Firstly, the rectangles are iterated in the for loop. For each rectangle its area is computed as f(x) * step. All the areas are summed up in result. The variable x is used to adapt the x-coordinate to the loop variable.

Now let us do this the functional way:

fun integrateFunctional(start :Double, end : Double, precision: Int,
                        f : (Double) -> Double) : Double {
    val step = (end-start) / precision
    return (0 until precision)
            .map { index -> start + index * step}
            .map { x -> f(x) * step }
            .sum()
}

The first surprise is that the range expression (0 until precision) which is used in the for loop can also be used as container for the functional style described above. Now, the phases in the computation are clearly separated. First, the index is mapped to x-coordinates, then the areas of the rectangles are computed, and finally summed up.

IMHO this is much better readable than the for loop. But all the interpretations, I gave above, are not in the code. Thus, to enhance the readability of the code, to make it much cleaner, you can do what you typically do: extract variables with meaningful names:

fun integrateFunctionalCleanCode(start :Double, end : Double, precision: Int,
                                 f : (Double) -> Double) : Double {
    val step = (end-start) / precision
    val xCoordinates = (0 until precision)
            .map { index -> start + index * step }
    val allRectangles = xCoordinates
            .map { x -> f(x) * step }
    return allRectangles
            .sum()
}

Now, the domain knowledge is represented in the variables names, and still the code is functional. This seems trivial, but in my experience, when people, such as myself, discover the functional coding style based on streams, they tend to build long und longer stream concatenations. At some tipping point they are not readable anymore and comments are included. This is latest point in time where I would advise to extract variables. The Clean Code principles also applies for functional programming!

What about Performance?

One of the usual counter argument from the imperative guys is that functional programming is slow. The best way to handle this question is to do some measurements. On my (old) MacBook Pro 2015, I measured the execution times for different precisions, each 5 times, and averaged them. These are the results:

Comparison of the functional and imperative implementations

Both axes are logarithmically scaled. Because the computation scales linearly the graphs should be linear too. The first surprise is the speedup in the imperative execution times at precision 10^6. Some magic loop optimization seems to kick in, leaving the functional implementation far behind, by a factor of nearly 200. Until the precision 10^5, the speed of the two implementations are pretty similar. I used Kotlin in version 1.3.40.

But, the real issue is the missing data point at precision 10^8. The point is missing because the JVM crashed with an OutOfMemoryError exception. To understand this you have to look at the type of xCoordinates, which is ArrayList. This means all coordinates and all rectangles are stored in an array, consuming memory. For a large loop this is definitely not what you want!

So, the case for functional programming is all lost? No, wait. Kotlin supports sequences . These are in essence lazily evaluated and potentially infinite lists. This is similar to how Haskell works efficiently on large sets of data. The refactoring to sequences is simple. You just hav to add one method call:

fun integrateFunctionalSequence(start: Double, end: Double, precision: Int,
                                f: (Double) -> Double): Double {
    val step = (end - start) / precision
    val xCoordinates = (0 until precision).asSequence()
            .map { index -> start + index * step }
    val allRectangles = xCoordinates
            .map { x -> f(x) * step }
    return allRectangles
            .sum()
}

Now let us look, if this improves anything:

Comparison of two functional and one imperative implementations

Firstly, the application does not crash anymore for a precision of 10^8. If you look at the first two data points, you can see that the sequence approach is faster than our first functional implementation. But, when the optimization kicks in, the imperative version is with a factor of 10 still much faster.

To conclude, yes, Kotlin is still much more optimized for imperative programming. If performance is an issue and if there are a lot of computations to perform, you should consider good old looping.

One short look at the old Champion again

To check if this means, functional programming is not fit for numerical computations at all, let us do the same algorithm in Haskell:

integrate start end precision function = sum allRectangles 
  where
    step = (end - start) / (fromIntegral precision)
    xCoordinates = map (\i -> start + (fromIntegral i) * step)
                     [ 0 .. (precision-1)]
    allRectangles = map (\x -> (function x) * step) xCoordinates

Not surprising, the Haskell code looks pretty similar to its Kotlin variant. Actually the explicit conversion of datatypes with fromIntegral looks somehow old school. Now let us do some measurements:

Comparison of two functional, one imperative implementations, and Haskell

Now, this is a nice curve. Firstly, Haskell finally provides the expected linear curve. Up to 10^5 iterations it is much faster than Kotlin. With the optimization at 10^6 iterations, it as as fast as Kotlin.

To sum it up

In my point of view, I have shown that in Kotlin you can use the functional programming style as good as in a pure functional language such as Haskell. For most common small problems, it as fast as imperative solutions. If you have more computional demands you need to take care to use sequences. Still, if you really want to do number crunching, Kotlin in version 1.3.40 needs some more performance tuning to support functional programming.

This was all focused on bounded computations. When I find some more spare time, I will write down an article about unbounded computations. The code of all examples is available on Github: https://github.com/akquinet/functional-loops-in-kotlin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s