Comparing Clojure to Kotlin

December 16, 2018
17 minutes.

One way to learn a new language is to compare it to one you already know. Here I’m taking a program written in ClojureThe code is taken from my colleague Richard Wild’s excellent series of articles on Functional Programming. and comparing it with similar code in Kotlin. The objective is two-fold, firstly to help me learn Clojure and secondly to see the similarities and differences between these two languages. In the following I’ll assume some minimum knowledge of both.

To warm up lets compare these two different ways of defining a function in Clojure:

(defn greet [name] (str "Hello, " name))

(def greet (fn [name] (str "Hello, " name)))

With the Kotlin equivalent:

fun greet(name: String) = "Hello, " + name

val greet = { name: String -> "Hello, " + name }

The syntax is quite different, of course, and Kotlin requires typed parameters where Clojure is dynamically typed. This can be a blessing and a curse but that’s a discussion for another day. Idiomatic Kotlin would also use built-in string templates instead of concatenation but the similarities nonetheless stand out.

Let’s get into the example. First define the neighbour offsets. In Clojure vector instantiation is quite neat (this is formatted manually to show the pattern).

(def neighbours
  [[-1,  1] [0,  1] [1,  1]
   [-1,  0]         [1,  0]
   [-1, -1] [0, -1] [1, -1]])

(defn neighbours-of [x y]
  (set (map (fn [[x-offs y-offs]] [(+ x-offs x) (+ y-offs y)])
            neighbours)))

(defn generate-cell [neighbours y x]
  (if (contains? neighbours [x y]) 1 0))

In Kotlin to do the same is slightly more verboseBut not as verbose a Java :) . We can use Pairs (tuples) and typealiases here to improve the readability.

typealias Cell = Pair<Int, Int>
typealias Cells = Collection<Cell>

var neighbours =
        setOf(Cell(-1,  1), Cell(0,  1), Cell(1,  1),
              Cell(-1,  0),              Cell(1,  0),
              Cell(-1, -1), Cell(0, -1), Cell(1, -1))

fun neighboursOf(x: Int, y: Int) =
        neighbours.map { (xOff, yOff) -> Cell(xOff + x, yOff + y) }

fun generateCell(neighbours: Cells, y: Int, x: Int) =
        if (neighbours.contains(Cell(x, y))) 1 else 0

The thing to note is the use of the map function in both implementations. The parameters are effectively reversed. We’ll see some implications of this later. Next we’ll see some currying (the focus of the article):

(defn generate-line [neighbours width y]
  (map (partial generate-cell neighbours y)
       (range 0 width)))

(defn generate-board [dimensions neighbours]
  (mapcat (partial generate-line neighbours (dimensions :w))
          (range 0 (dimensions :h))))

And in Kotlin:

fun generateLine(neighbours: Cells, width: Int, y: Int) =
        (0 until width).map { x -> generateCell(neighbours, y, x) }

fun generateBoard(h: Int, w:Int, neighbours: Cells ) =
        (0 until h).flatMap { y -> generateLine(neighbours, w, y) }

Things to note:

The next bit is much the same:

(defn mine? [cell]
  (= \* cell))

(defn board-for-cell [dimensions y x cell]
  (generate-board dimensions (if (mine? cell) (neighbours-of x y))))

(defn boards-for-line [dimensions line y]
  (map (partial board-for-cell dimensions y)
       (range 0 (dimensions :w))
       line))

Compared to the Kotlin version:

fun isMine(cell: Char) =
        '*' == cell

fun boardForCell(h:Int, w:Int, y:Int, x:Int, cell:Char) =
        generateBoard(h, w, if(isMine(cell)) neighboursOf(x,y) else emptyList())

fun boardsForLine(h:Int, w:Int, line:CharSequence, y:Int) =
        (0 until w).map { x -> boardForCell(h, w, y, x, line[x]) }

Here we can again see the difference between the map functions mentioned earlier. Clojure allows multiple ranges for the map operation, essentially ziping them together for you. It’s an interesting idea. Note that Kotlin also requires that boardForCell() return a result where Closure does not, defaulting to nil. Kotlin spurns nulls and in any case I prefer this approach.

Now the fun part.

(defn sum-up [& vals]
  (reduce + vals))

(defn draw [input-board]
  (let [lines (str/split-lines input-board),
        dimensions {:h (count lines), :w (count (first lines))}]
    (->> (mapcat (partial boards-for-line dimensions)
                 lines
                 (range 0 (dimensions :h)))
         (apply map sum-up))))

This combines several concepts that were new to me, ->> and apply together with the tricky (for me) map function and some magic with the varadic arguments. It took a while to understand what was going on here and in fact my first couple of naïve attempts didn’t work at all. Since the aim was to get as close to the Clojure code as possible this is what I finally came up with:

fun sumUp(vararg vals: List<Int>) =
        vals.reduce { acc, v -> acc.zip(v) { a, b -> a + b } }

fun draw(inputBoard: CharSequence): List<Int> {
    val lines = inputBoard.split("\n")
    val h = lines.size
    val w = lines.first().length
    val boards = (0 until h).flatMap { y -> boardsForLine(h, w, lines[y], y) }
    return sumUp(*boards.toTypedArray())
}

Things to note:

fun List<List<Int>>.sumUp() =
        this.reduce { acc, v -> acc.zip(v) { a, b -> a + b } }

fun draw(inputBoard: CharSequence): String {
    val lines = inputBoard.split("\n")
    val h = lines.size
    val w = lines.first().length
    return (0 until h).flatMap { y -> boardsForLine(h, w, lines[y], y) }
            .sumUp()

The rest of the exercise consists of manipulating and merging the output to give the final result:

(defn cell-as-text [cell-value]
  (if (zero? cell-value) \space (str cell-value)))

(defn overlay-cell [top bottom]
  (if (mine? top) top bottom))

(defn overlay-boards [top bottom]
  (reduce str (map overlay-cell top bottom)))

(defn draw [input-board]
  (let [lines (str/split-lines input-board),
        dimensions {:h (count lines), :w (count (first lines))}]
    (->> (mapcat (partial boards-for-line dimensions)
                 lines
                 (range 0 (dimensions :h)))
         (apply map sum-up)
         (map cell-as-text)
         (partition (dimensions :w))
         (map (partial reduce str))
         (interpose \newline)
         (reduce str)
         (overlay-boards input-board))))

Translated directly to Kotlin’s built-in functions it looks something like this:

fun cellAsText(cellValue: Int) =
    if (cellValue == 0) " " else cellValue.toString()

fun overlayCell(top: Char, bottom: Char) =
    if (isMine(top)) top else bottom

fun draw(inputBoard: CharSequence): String {
    val lines = inputBoard.split("\n")
    val h = lines.size
    val w = lines.first().length
    return sumUp((0 until h).flatMap { y -> boardsForLine(h, w, lines[y], y) })
            .map(::cellAsText)
            .chunked(w)
            .map { it -> it.joinToString("") }
            .joinToString("\n")
            .zip(inputBoard)
            .map { (a,b) -> overlayCell(b, a) }
            .joinToString("")
}

Here Clojure’s partition function is replaced by Kotlin’s chunked function and str with string functions like joinToString. Once again Clojure’s multi-argument map function does not have a direct translation in Kotlin so we use zip to merge the result with the initial board to generate the final results. To get something more closely resembling thr Clojure code we can take advantage of Kotlin’s extension methods. We’ll need to create some helper methods.

fun str(a: CharSequence, b: CharSequence) = listOf(a, b).joinToString("")

fun List<CharSequence>.interpose(sep: CharSequence) =
        this.flatMap { it -> listOf(sep, it) }.drop(1)

An then use them to build the data pipeline:

fun CharSequence.overlayBoards(other: CharSequence) =
        other.zip(this, ::overlayCell).joinToString("")

fun draw(inputBoard: CharSequence): String {
    val lines = inputBoard.split("\n")
    val h = lines.size
    val w = lines.first().length

    return (0 until h).flatMap { y -> boardsForLine(h, w, lines[y], y) }
            .sumUp()
            .map(::cellAsText)
            .chunked(w)
            .map { it -> it.reduce(::str) }
            .interpose("\n")
            .reduce(::str)
            .overlayBoards(inputBoard)
}

Using extension methods has allowed us to put the other functions like interpose and overlayBoards into the pipeline giving us an effect similar to Clojure’s thread-last operator.

We can check the output by calling the draw function from main:

fun main(args: Array<String>) {
    println(draw("*   \n *  \n  * \n   *"))
}

And it does indeed, in case you’re wondering, give us the results we expect.

*21 
2*21
12*2
 12*

Conclusion

I was impressed by Clojure’s ability to simplify complex pipelines using the thread-last ->> operator. I needed to resort to extension methods to get the same effect in Kotlin. It’s a powerful thing and now I’m wondering why I haven’t seen it before. Maybe it’s only possible because of Clojure’s dynamic typing. The way the map operation is implemented is also very useful as compared to similar approaches in other languages.

I’m starting to get the gist of Clojure’s syntax (if only scratching the surface of it runtime). This exercise has helped really understand some non-trivial Clojure code. Next step will be to convert some existing functional style Kotlin to Clojure. That’s for another day.

Comparing Clojure to Kotlin - December 16, 2018 - John Hearn