Functional Kotlin starting from Java

July 25, 2020

This post is about the transition from Kotlin sourcecode that looks like java code to code that uses the functional Kotlin constructs.

Starting up

I will write about a completely made up class here but I experienced that you can do that with almost any class in the entire codebase.

Inspiration from the daily programmer: word funnel

Starting with Java

We start with the java implementation of the word funnel puzzle mentioned above

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static java.util.stream.Collectors.toList;

public class WordFunnel {
  public static void main(final String... args) {
    if(funnel("leave","eave")){
      System.out.println("funnel");
    }

  }

  private static boolean funnel(final String first, final String second) {
    final List<Integer> firstList = first.chars().boxed().collect(toList());
    for (int i = 0; i < firstList.size(); i++) {
      final List<Integer> tempList = new ArrayList<>(firstList);
      tempList.remove(i);

      if (join(tempList).equals(second)) {
        return true;
      }
    }
    return false;
  }

  private static String join(final List<Integer> tempList) {
    final List<String> chars = tempList.stream().map(x->(char)x.intValue()).map(Objects::toString).collect(toList());

    return String.join("", chars);
  }
}

Transform it to Kotlin

import java.lang.String.join
import java.util.*
import java.util.stream.Collectors.toList


fun main(args: Array<String>) {
    if (funnel("leave", "eave")) {
        System.out.println("funnel")
    }

}

private fun funnel(first: String, second: String): Boolean {
    val firstList = first.chars().boxed().collect(toList())
    for (i in 0..firstList.size) {
        val tempList = getSublist(firstList, i)

        if (joinChars(tempList) == second) {
            return true
        }
    }
    return false
}

private fun getSublist(firstList: List<Int>, i: Int): List<Int> {
    val tempList = ArrayList<Int>(firstList)
    tempList.removeAt(i)
    return tempList
}

private fun joinChars(tempList: List<Int>): String {
    val chars = tempList.stream().map { it.toChar().toString() }.collect(toList())

    return join("", chars)
}

The main difference is, that we dont have the class construct anymore and start the main() function directly.

We also changed the implementation of the getSublist() function and switched remove() to removeAt().

Start the cleansing

We start to optimize with the getSublist() function. We remove the local variable and use the Kotlin .apply{} construct here:

private fun getSublist(firstList: List<Int>, i: Int): List<Int> {
    return ArrayList<Int>(firstList).apply {
        removeAt(i)
    }
}

Now that this function only contains one call we can make it a single line function:

private fun getSublist(firstList: List<Int>, i: Int) =
        ArrayList<Int>(firstList).apply {
            removeAt(i)
        }

We apply the same treatment to the joinChars() function:

private fun joinChars(tempList: List<Int>) =
        tempList.stream().map { it.toChar().toString() }.collect(toList()).let {
            join("", it)
        }

Use extension functions to spare a parameter

We change joinChars() to an extension function for the generic class List<Int> to be able to call it directly:

private fun funnel(first: String, second: String): Boolean {
    val firstList = first.chars().boxed().collect(toList())
    for (i in 0..firstList.size) {
        val tempList = getSublist(firstList, i)

        if (tempList.joinChars() == second) {
            return true
        }
    }
    return false
}

private fun getSublist(firstList: List<Int>, i: Int) =
        ArrayList<Int>(firstList).apply {
            removeAt(i)
        }

private fun List<Int>.joinChars() =
        stream().map { it.toChar().toString() }.collect(toList<String>()).let {
            join("", it)
        }

Now we can ommit the tempList variable in the funnel() method:

private fun funnel(first: String, second: String): Boolean {
    val firstList = first.chars().boxed().collect(toList())
    for (i in 0..firstList.size) {
        if (getSublist(firstList,i).joinChars() == second) {
            return true
        }
    }
    return false
}

We also refactor the getSublist() method into an extension function of List<Int>:

private fun funnel(first: String, second: String): Boolean {
    val firstList = first.chars().boxed().collect(toList())
    for (i in 0..firstList.size) {
        if (firstList.getSublist(i).joinChars() == second) {
            return true
        }
    }
    return false
}

private fun List<Int>.getSublist(i: Int) =
        ArrayList<Int>(this).apply {
            removeAt(i)
        }

Refactor the for loop

We can remove the last local variable in the funnel() function and replace it with a .run{} call:

private fun funnel(first: String, second: String): Boolean {
    first.chars().boxed().collect(toList()).run {
        for (i in 0..size) {
            if (getSublist(i).joinChars() == second) {
                return true
            }
        }
        return false
    }
}

Inside the .run{} block this is now bound to the previous variable firstList and though we can omit it at the calls of the extension functions.

We can now also make this method singleline:

private fun funnel(first: String, second: String) =
        first.chars().boxed().collect(toList()).run {
            for (i in 0..size) {
                if (getSublist(i).joinChars() == second) {
                    return true
                }
            }
            false
        }

The two different return notations look quite ugly.

To get rid of these and replace the for loop we can use the (0..size).any{} construct:

private fun funnel(first: String, second: String) =
        first.chars().boxed().collect(toList()).run {
            (0..size).any {
                getSublist(it).joinChars() == second
            }
        }

Wrap it up

As you can see our general goal was to reduce complexity by removing local variables. To archieve this we used the following techniques:

Gist source code: WordFunnel