Photo from Log Horizon

Kotlin tools - Currying

Posted: 12 Jul 2021. Last modified on 04-Jun-22.

This article will take about 4 minutes to read.

Currying a function allows us to reduce the number of parameters that a function requires. We can do this by returning a function that has some of the original information embedded within it. This is a functional programming technique called creating a closure.

This is a helper function which will allow you to curry:

fun <A, B, R> ((A, B)->R).curry(a: A): (B)->R = { b: B -> this(a, b) }
fun <A, R> ((A)->R).curry(t: A): ()->R = { this(t) }

and, for the sake of this demo, we can use the following function to show how this will look in action:

fun f(i: Int, s: String) { print(`$i $s`) }

Watch how we can now create new functions which perform the same actions, using fewer parameters:

f(1, "test")
//> 1 test

val b = ::f.curry(2)
//> 2 test

Notice how function b requires one fewer argument than calling function f explicitly. The ::f syntax is a function reference, that lets us use f as if it were a variable. We will not need to use this syntax once we are dealing with real variables later on.

We can keep currying until we get down to 0 parameters, at which point we’ve stored all of the state that we need for a full invocation.

val c = b.curry("test")
//> 2 test

In this case, c requires no arguments because we have curried the original function twice, removing both of the original function’s parameters. The number 2 was passed in when we created variable b!

When would I use this?

Creating helper methods for using injected objects

Currying can encourage code reuse and simplification. Used correctly, it sets your code up to be easy to refactor later. Currying a function reduces its dimensionality, and allows us to prepare a generic function to be called for similar tasks.

See this example of decoupling calls to a database:

fun writeToGenericDB(db: Database, item: Item) {
  db.use { it.commit(item) }

val database: NoSqlDB by inject()
val writeToDB = ::writeToGenericDB.curry(database)

By injecting the database instance into the original function and currying it, we remain decoupled (we can always inject a different instance into the original function), while at the same time guaranteeing that the same injected instance is used throughout the file. This leaves us open to switching database instances without having to make changes elsewhere in the code.

Creating prefixes for logging utilities

Imagine that we had a logging utility which accepted records of this form:

fun log(dateFormatter: DateFormat, message: String) {
  println("${dateFormatter.format(Calendar.getInstance().getTime())}: $message")

with currying we could easily remove this the clunky first parameter, via

val logWithDate = when {
  buildConfig.isEuro -> ::log.curry(SimpleDateFormat("dd/MM/yyyy"))
  buildConfig.isAmerican -> ::log.curry(SimpleDateFormat("MM/dd/yyyy"))

we could then use them like so - reducing duplication and making the code more understandable

//> 07/31/2021: Hello
// OR, depending on geo
//> 31/07/2021: Hello