Kotlin Demystified: Understanding Shorthand Lambda Syntax

Photo by Stefan Steinbauer on Unsplash

During a trip to Austria, I visited the Austrian National Library in Vienna. The State Hall, in particular, is this amazing space that feels like something out of an Indiana Jones film. Spaced around the room are these doors built into the shelves, and it’s tempting to imagine what sort of secrets are hidden behind them.

As it turns out, however, they’re simply reading rooms.

Let’s imagine we have an app that tracks the books in the library. One day we’re wondering what the longest and shortest books in the collection are. After a bit, we write the code that allows us to find both of these:

val shortestBook = library.minBy { it.pageCount }
val longestBook = library.maxBy { it.pageCount }

Perfect! But this got me wondering, how do these methods work? How does it know, just from writing it.pageCount, how to do this?

The first thing I did was to click through to the definitions of minByand maxBy, which are both in _Collections.kt. Since they’re both nearly identical, let’s focus on maxBy, which starts on line 1559.

The method there is build on the Iterable interface, but if we do a minor rewrite to work with Collections, and perhaps rename some variables to be a bit more verbose, it’s easier to follow:

public inline fun <T, R : Comparable<R>> Collection<T>.maxBy(selector: (T) -> R): T? {
if (isEmpty()) return null
var maxElement = first()
var maxValue = selector(maxElement)
for (element in this) {
val value = selector(element)
if (maxValue < value) {
maxElement = element
maxValue = value
}
}
return maxElement
}

We can see that it’s just grabbing each element in the Collection, checking if the value, from the selector, is greater than the max value its seen. If it is, it saves both the element and value. At the end, it returns the largest element it found. Rather simple.

selector, however, looks kind of neat, and it must be the thing that allows us to use it.pageCount above, so let's look into it some more.

There’s even quite a bit of syntactic sugar even just in this bit of the line. selector: (T) -> R is the short way to say a function that takes a single parameter, T in this case, and returns something of type R.

The way that works is Kotlin includes a set of interfaces called FunctionN, where N is the number of parameters it accepts. Since ours has one, we can implement the Function1 interface and then use that in our code:

class BookSelector : Function1<Book, Int> {
override fun invoke(book: Book): Int {
return book.pageCount
}
}

val longestBook = library.maxBy(BookSelector())

That certainly shows how it works easily enough. selector is a Function1 that, when given a Book, returns an Int. Then, maxBy takes the Int and compares it to the value it has.

This, incidentally, also explains why the generic parameter R has the type R [implements] Comparable<R>. If R weren't Comparable, we couldn't do if (maxValue < value).

The next question then is, how do we go from that, to the one liner we started with? Let’s step through the process.

First, the code can be replaced with a lambda, which already shrinks it quite a bit:

val longestBook = library.maxBy({
it.pageCount
})

The next step is that if the last parameter of a method is a lambda, we’re allowed to close the parentheses and then add the lambda to the end of the line, like this:

val longestBook = library.maxBy() {
it.pageCount
}

Finally, if a method only takes a lambda parameter, we’re allowed to completely leave off the () from the method, which gets us back to our initial code:

val longestBook = library.maxBy { it.pageCount }

But wait! What about that Function1! Am I performing an allocation whenever I use this?

That’s a great question! The good news is, no, you’re not. If you look again, you’ll see that maxBy is marked as an inline function. This happens at the source level during compilation, so while there's more code compiled than it might initially look like there is, there aren't any significant performance impacts, and certainly no object allocations.

Awesome! Now we not only know what’s the shortest (and longest) books are in the library, we also better understand how maxByworks. We saw how Kotlin uses FunctionN interfaces for lambdas, and how it's sometimes possible to move a lambda expressions outside the parameter list of functions. Finally, we learned that it's possible to completely omit the parenthesis normally used when calling functions when there's only a single, lambda parameter.

Check out the Google Developers blog for more great content, and stay tuned for more articles on Kotlin!