fold() in Kotlin is like JavaScript reduce() but slightly different

fold() in Kotlin is like JavaScript reduce() but slightly different

ยท

5 min read

If you've caught a few of my coding streams, you may know that I'm a huge fan of the Kotlin programming language.

Kotlin allows you to follow functional patterns found in languages like JavaScript, Ruby and Python but with compile-time type safety.

reduce() in JavaScript

You are likely already familiar with .reduce() in JavaScript, which may be why you are reading this post.

If not, here's an example that takes a list of integers and returns the sum:

const numbersToSum = [1, 2, 3, 4, 5]
const summedValue = numbersToSum.reduce((acc, curr) => acc + curr) // 15

The resulting summed value is 15 in this case. You can also make this a reusable function (the return values are in the comments):

function sum(total, curr) {
    return total + curr
}

// implement
const summedA = [1, 2, 3].reduce(sum) // 6
const summedB = [4, 5, 6].reduce(sum) // 15
const summedC = [7, 8, 9].reduce(sum) // 24

reduce() in Kotlin

We can achieve something similar with the .reduce() function in Kotlin:

val numbersToSum = listOf(1, 2, 3, 4, 5)
numbersToSum.reduce { acc, curr -> acc + curr }

The Kotlin version is even more concise. The return type of Int is inferred since .reduce() is being called on a list of integers.

Caveats with reduce() in Kotlin

There's a problem with using reduce() in Kotlin. If the list is empty, then .reduce() doesn't know what value to return so you'll get a runtime exception. So, given the following code:

val numbersToSum = listOf<Int>()
numbersToSum.reduce { acc, curr -> acc + curr }

It will throw this exception:

Exception in thread "main" java.lang.UnsupportedOperationException: Empty collection can't be reduced.
 at FileKt.main (File.kt:10) 
 at FileKt.main (File.kt:-1) 
 at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (:-2)

Here's a link to a Kotlin playground.

So how do we fix this? That's where fold() comes in! See below.

fold() in Kotlin

The fold() method in Kotlin allows you to pass an initial value to the function, which will solve this problem entirely. This is also more similar to how the JavaScript function works, the only difference is the argument order is reversed in Kotlin to allow for the trailing lambda syntax. Here's an example of the same functionality using fold():

val numbersToSum = listOf<Int>()
numbersToSum.fold(0) { acc, curr -> acc + curr }

This means that 0 will be the value of acc (the accumulated value) on the first iteration.

Here's the same code in a function we can reuse:

fun sum(total: Int, curr: Int): Int {
    return total + curr
}

val numbersToSum = listOf<Int>(1, 2, 3, 4, 5)
numbersToSum.fold(0, ::sum)

Kotlin even has some syntactic sugar you can use to make this a one-liner:

fun sum(total: Int, curr: Int): Int = total + curr

val numbersToSum = listOf<Int>(1, 2, 3, 4, 5)
numbersToSum.fold(0, ::sum) // 15

A real world example

On my programming streams on Twitch, I've added some channel point redemptions that allows users to check their daily horoscope. Sometimes, unfortunately, there are typos that can commonly occur, especially for mobile users with autocorrect.

One example of this is iPhone's autocorrect will very unhelpfully autocorrect "aquarius" to "aquarium," which is an invalid sign and causes an error in the channel point redemption evaluation.

I wanted my channel point redemption to accommodate common typos. Right now, in the first iteration at least, the logic is quite naive and I included the word "aquarium" explicitly, which is good enough for an application of this nature.

I used Kotlin's collections fold method (or more accurately, the collections foldIndexed method so I can access the index) to help solve this for me. Please note, if searching on the Kotlin docs directly, Kotlin's Result fold is the first hit, which is not the right one, though it is also quite interesting and I use that too.

Anyhow, here's the example of using Kotlin's fold() instead of reduce() in my real-world example. The below code assigns an integer to every sign, which is required by the API that I am using:

fun main() {
    val indexCorrect = indexForSign("aquarius")
    val indexTypo = indexForSign("aquarium")

    println("Correct: $indexCorrect - Typo: $indexTypo")
}

val horoscopeValidSigns = listOf(
    "aries", "taurus", "gemini", "cancer",
    "leo", "virgo", "libra", "scorpio",
    "sagittarius", "capricorn", "aquarius", "pisces"
)

val signIndexMap: Map<String, Int> get() =
    horoscopeValidSigns
    .foldIndexed(mutableMapOf<String, Int>()) { idx, acc, curr ->
        acc[curr] = idx
        acc
    }
    .apply {
        // Overrides to support typos
        put("aquarium", 10)
    }

fun indexForSign(sign: String): Int = 
    signIndexMap[sign.lowercase()] ?: -1

We can use fold(mutableMapOf<String, Int>()) to provide an initial value of an empty mutable map where the keys are strings (the sign name) and the value is the number (in this case, the index in a list).

Then, after the map is created, we can call a handy function in Kotlin called .apply {} which allows us to call methods on the object that we are applying to.

Calls the specified function block with this value as its receiver and returns this value.

So what that means is you can call myMap.apply {} and in the block, call put(k, v) to add items to the map, and the function will automatically return myMap. The apply() function is very handy in Kotlin.

You can see this code and run it in this Kotlin playground.

Learning More

If you found this helpful or interesting, you may be interested in following me on Twitch to catch a programming stream. Please note, I also play games on my stream, so you are also welcome to pop by a gaming stream and chat about programming.

You may also find the following collections on Twitch interesting:

Kotlin isn't the only language I do on stream, I also use Rust and Go. In my streams I also try to keep discussions around programming languages healthy, so on my stream we appreciate all languages.

Pop by to hang out while I code or game. See you there!

ย