· 7 min read
Function references in Kotlin: use functions as lambdas everywhere
Function references are another of those great improvements that we get with Kotlin, which are kind of exotic when we come from Java.
You already know that Kotlin supports functions as a type, what means that you can save a function in a variable, use it as another function argument, or even make that a function returns another function.
This is the main feature to consider that a language supports functional programming style, and Kotlin of course allows this. You can declare a function in a variable like this:
val sum: (Int, Int) -> Int = { x, y -> x + y }
See? This is a function that receives two integers and returns one integer. In this particular implementation, the lambda is applying an addition operation.
You could then have a function that accepts a lambda of this type as an argument:
fun applyOp(x: Int, y: Int, op: (Int, Int) -> Int): Int = op(x, y)
This is taking two integers and applying the function to both, so you can use the combination of both this way:
applyOp(2, 3, sum)
Easy, right? You probably knew about this already. But I’m gonna show you something interesting now:
Function references: any function can be a lambda
So the same way lambdas can be passed as an argument or saved to a variable, we can do the same with regular functions.
Thanks to function references, our code can become much cleaner, and we can apply a functional style to libraries or frameworks (can you think of any? 😝) that use just plain functions.
I’ll first explain it with the example above. Imagine that instead of a lambda, you have a plain function:
fun sum(x: Int, y: Int) = x + y
This is doing the same, but instead of having a variable that keeps the function, we just have a function. Now, if you look at the call above, it is failing:
applyOp(2, 3, sum)
sum
is not detected as a lambda, it just expects to be called as a regular function. But if you think about it carefully, it keeps the same structure: it receives two integers and returns one. What are we missing?
We need to use the function reference. For that, you only have to prepend two colons to the name of the function:
applyOp(2, 3, ::sum)
And done!
So that you get the complete idea of this, a function reference behaves as a lambda, and as such, you can assign this reference to a variable with the same structure:
val sumLambda: (Int, Int) -> Int = ::sum
Awesome, right? But let’s see this in some more practical cases.
Putting functional references into practice
If it’s the first time you see this, you may be wondering what gives you that you didn’t have before. Fair question, let’s see some examples.
Imagine the typical example where you receive some data to update the UI, but you only need to do it in the case the data is not null. The most direct approach would be:
private fun updateUI(data: Data?) {
if(data != null){
applyUiChanges(data)
}
}
private fun applyUiChanges(data: Data) {
// Do cool stuff in UI
}
If you already know about the let
function, you’ll agree with me that this looks a little better:
data?.let { applyUiChanges(data) }
But let’s think about this for a moment… If you check the structure of the let
function (don’t worry if you don’t understand everything):
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
It’s a function that receives a lambda with two arguments: T
and R
. In our particular case T
would be Data
and R
is Unit
, as we’re not using the return value of let
for anything. So it would look like this after computing the generic types:
public inline fun Data.let(block: (Data) -> Unit) = block(this)
Remember about the function applyUiChanges
? It receives Data
and returns nothing (what it’s the same as Unit
), so it conforms with the structure that the let
function is expecting. So why don’t we call the function instead?
data?.let(::applyUiChanges)
W00t! If you think about it this is much cleaner, and explains better what it’s happening. It’s true that first times it may look a little strange, but as soon as you get used to it, you’ll love it.
The rule of thumb: when in a lambda we use a function that receives all the input values of the lambda as arguments, then you can just use the function reference.
But let’s go beyond!
Lambdas with more than one input value
Of course, this works with any amount of values. Imagine that you have an observable delegate like this:
var items: List<MediaItem> by Delegates.observable(emptyList()) { property, oldValue, newValue ->
notifyChanges(oldValue, newValue)
}
As you see, we cannot just use the reference to the function because notifyChanges
doesn’t have the same structure. It’s only receiving two values instead of three. But just by changing the structure of the function a little:
fun notifyChanges(property: KProperty<*>, oldValue: List<MediaItem>,
newValue: List<MediaItem>) {
...
}
Now we’re able to use the reference instead:
var items: List<MediaItem> by Delegates.observable(emptyList(), ::notifyChanges)
Property references
References are not only restricted to functions. Properties, in the end, are functions that return a value (think of them as the getters in Java), so why not using them?
Let’s say we have a data class called MediaItem
:
data class MediaItem(val title: String, val url: String)
And that we want to print a list of URLs sorted by the title of the item (You can check more about functional collection operations here):
items
.sortedBy(MediaItem::title)
.map(MediaItem::url)
.forEach(::println)
In my opinion, this is is easier to read than the counterpart with explicit lambdas:
items
.sortedBy { it.title }
.map { it.url }
.forEach { print(it) }
Looking at the bytecode
You might be wondering if there’s any difference in bytecode, and if one of the options is more efficient than the other. So let’s use the Kotlin bytecode tool for the previous example and check it out.
This is the bytecode for the one with explicit lambdas:
Iterable $receiver$iv = (Iterable)items;
$receiver$iv = (Iterable)CollectionsKt.sortedWith($receiver$iv, (Comparator)(new HomeContentFragmentKt$getSortedUrls$$inlined$sortedBy$1()));
Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10)));
Iterator var4 = $receiver$iv.iterator();
while(var4.hasNext()) {
Object item$iv$iv = var4.next();
MediaItem it = (MediaItem)item$iv$iv;
String var11 = it.getUrl();
destination$iv$iv.add(var11);
}
$receiver$iv = (Iterable)((List)destination$iv$iv);
Iterator var2 = $receiver$iv.iterator();
while(var2.hasNext()) {
Object element$iv = var2.next();
String it = (String)element$iv;
System.out.print(it);
}
And this for the one with function references:
Iterable $receiver$iv = (Iterable)items;
$receiver$iv = (Iterable)CollectionsKt.sortedWith($receiver$iv, (Comparator)(new HomeContentFragmentKt$getSortedUrls$$inlined$sortedBy$1()));
Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10)));
Iterator var4 = $receiver$iv.iterator();
while(var4.hasNext()) {
Object item$iv$iv = var4.next();
String var10 = ((MediaItem)item$iv$iv).getUrl();
destination$iv$iv.add(var10);
}
$receiver$iv = (Iterable)((List)destination$iv$iv);
Iterator var2 = $receiver$iv.iterator();
while(var2.hasNext()) {
Object element$iv = var2.next();
System.out.println(element$iv);
}
Unsurprisingly, the code is pretty similar. With the function references, we’re saving ourselves from a couple of variable declarations, which correspond to the it
variables inside the lambdas. So we can say it’s a little more efficient, but honestly, nothing to worry about.
Should I use function references in my code?
Function references are a cool new feature that can clean up code and do it more semantic.
But this comes at the cost of increasing the learning curve. It’s something Java developers are not used to, and because of that, new Kotlin adopters can find them a bit obscure at the beginning.
But hey! You can always link them to this article, and have them writing them in a few minutes 😄
Did you know about function references? Have you used them in your projects? Let me know in the comments, and if you’re in the process of learning Kotlin, you may be interested in my online course.