Kotlin: An Illustrated Guide • Chapter 7

Lambdas and Function References

Chapter cover image

As we’ve seen, functions are a basic building block of Kotlin code. Throughout this series, we’ve written a lot of them, all using the fun keyword. Kotlin also gives us another way to write functions - lambdas!

To help us learn about function types, function references, and lambdas, we’ll need to pay a visit to Bert’s Snips & Clips.

Bert’s Snips & Clips

Bert’s Snips & Clips is the salon to go for a clean haircut and a nice smooth shave. Although he prides himself on his low prices, sometimes he offers coupons that give the customer $5 off, to lower the price even further.

Drawing of a coupon for $5 off.

Once the customer has their fresh new haircut, he needs to calculate the total cost, so that he can charge the customer the right amount. Bert’s pretty good at math, but to make this fast and easy, he created a simple Kotlin function to calculate the total.

His function needed to account for tax and the five-dollars coupon. Here’s what he wrote:

// Tax is 9%, so we'll multiply by 109% to get the total with tax included. 
val taxMultiplier = 1.09 

fun calculateTotalWithFiveDollarDiscount(initialPrice: Double): Double {
    val priceAfterDiscount = initialPrice - 5.0 
    val total = priceAfterDiscount * taxMultiplier

    return total 
}

If the customer has a $20 haircut and presents a $5 off coupon, he simply runs the function like this:

fun main() {
    val total = calculateTotalWithFiveDollarDiscount(20.0) 
    println("$%.2f".format(total)) 
}

When he runs this code, it prints $16.35.

One day, one of Bert’s customers spent a lot of money on haircuts for the whole family, and she was disappointed that they only had a single coupon for five dollars off. In order to reward his most loyal, high-paying customers, he decided to introduce a 10% off coupon. That way, the more money they spend at his salon, the more dollars would be discounted.

Drawing of two coupons - one for $5 off and one for 10% off.

Alongside the original function, Bert created a second function, which accommodates the new percent-based coupon. To calculate the price after a 10% discount, he just multiplies the initial price by 90%.

fun calculateTotalWithFiveDollarDiscount(initialPrice: Double): Double {
    val priceAfterDiscount = initialPrice - 5.0
    val total = priceAfterDiscount * taxMultiplier

    return total
}

fun calculateTotalWithTenPercentDiscount(initialPrice: Double): Double {
    val priceAfterDiscount = initialPrice * 0.9
    val total = priceAfterDiscount * taxMultiplier

    return total
}

When he looked at these functions, he realized that they’re almost exactly the same, except for one small part - the part that calculates the discounted price.

Annotated code highlighting the only difference between the functions in Listing 7.3. fun calculateTotalWithFiveDollarDiscount (initialPrice: Double): Double { val priceAfterDiscount = initialPrice - 5.0 val total = priceAfterDiscount * taxMultiplier return total } fun calculateTotalWithTenPercentDiscount (initialPrice: Double): Double { val priceAfterDiscount = initialPrice * 0.9 val total = priceAfterDiscount * taxMultiplier return total }

If only he could find a way to pass code as an argument… then he could consolidate his two functions down to one function that would look something like this:

fun calculateTotal(
    initialPrice: Double,
    applyDiscount: ???
): Double {
    val priceAfterDiscount = applyDiscount(initialPrice)
    val total = priceAfterDiscount * taxMultiplier

    return total
}

In other words, he just wants to tell calculateTotal() to apply a discount in a different way each time he calls it. If he could pass a function as an argument to calculateTotal(), that would solve his problem. But passing a function as an argument to another function? How could that be possible?

With Kotlin’s function types, it’s easy!

Introduction to Function Types

We saw way back in Chapter 1 how we could create variables that hold values. For example, to create a simple String variable that holds text, we can do this:

Annotated code showing what a variable is.valname: String ="Bert"

Similarly, parameters are variables in a function where the calling code passes in their values when invoking the function. For example:

Annotated code showing what a parameter is.funintroduce(name: String) {println("My name is$name")}introduce("Bert")

So far, every time that we’ve assigned a variable or passed an argument to a function, we’ve been dealing with values of simple types like String, Int, and even more complex objects of our own types, like Circle.

In addition to assigning simple values like these, Kotlin also lets you assign functions.

To demonstrate this, here’s a simple function to calculate a flat dollar discount, like a $5-off coupon:

fun discountFiveDollars(price: Double): Double = price - 5.0

As you already know, you can call this function, like so:

val discountedPrice = discountFiveDollars(20.0) // Result is 15.0

In addition to calling this function, we can also assign it to a variable like this.

val applyDiscount = ::discountFiveDollars

Notice the difference between these two lines of code. In Listing 7.6, we’re assigning the result of a function call, but in Listing 7.7, we’re assigning the function itself.

In the code above, ::discountFiveDollars is a function reference. We call it that because it refers to a function. By assigning this function to a variable, it’s kind of like giving discountFiveDollars() another name. Now we can call applyDiscount() the same way that we called discountFiveDollars(), and it does the same thing.1

val discountedPrice = applyDiscount(20.0) // Result is 15.0

Whether we call discountFiveDollars() in Listing 7.6 above, or call applyDiscount() here, the result is the same: 15.0 (that is, $15.00).

As you might recall, every time that we declare a variable, that variable has a type, even if it’s not explicitly written out in the code. For example:

val name = "Bert"    // name's type is String
val hasCoupon = true // hasCoupon's type is Boolean
val price = 12.50    // price's type is Double

So then, you might be wondering about the type of the applyDiscount variable.

When a variable holds a function, its type is a combination of all of its parameter types and its return type. In the case of the discountFiveDollars() function, these include a single parameter of type Double and a return type of Double.

Annotated code showing the parameter type and the return type.fundiscountFiveDollars(price: Double): Double = price -5.0

A function’s type can be assembled by:

  1. Keeping its parentheses
  2. Keeping its types
  3. Converting the colon : to an arrow ->.

Let’s do that with discountFiveDollars().

How to figure out the type of a function.> Double(Double)-fundiscountFiveDollars(price: Double): Double = price -5.0

So, the type of discountFiveDollars() is (Double) -> Double. Knowing this, you can now write the type of applyDiscount explicitly:

val applyDiscount: (Double) -> Double = ::discountDollars

All of the types that we’ve seen up until now have had no spaces in them, like String, Int, and Double. As you can see, function types like (Double) -> Double are a bit long, but they’re easy to figure out! The parameter types go inside the parentheses, and the result type goes to the right of the arrow.

The different parts of a function type.> Double(Double)-

For functions that have multiple parameters, separate the parameter types with a comma. For example, here’s a function that has two parameters - a String and a Double - and it returns a Double. When assigning this function to a variable, that variable’s type will be (String, Double) -> String.

fun menuItemDescription(name: String, price: Double): String =
    "A $name costs $price before discounts and tax."

val describeMenuItem: (String, Double) -> String = ::menuItemDescription

Two functions of the same type

As you might recall, if you have two values of the same type, such as two String values, then you can assign (and reassign) those two String values to the same variable.

var couponCode = "FIVE_BUCKS"
couponCode = "TAKE_10"

Similarly, if you have two functions of the same type - that is, functions that have the same parameter types and return type - then you can assign (and reassign) those two functions to the same variable.

To demonstrate this, let’s introduce a function to calculate the discount for a 10%-off coupon. Since it has the same parameter and result types as discountFiveDollars(), we can assign either of these functions to the same variable.

fun discountFiveDollars(price: Double): Double = price - 5.0
fun discountTenPercent(price: Double): Double = price * 0.9

var applyDiscount = ::discountFiveDollars
applyDiscount = ::discountTenPercent

Note that the parameter names do not have to match. Just the types have to match. This code works just the same as the code above:

fun discountFiveDollars(initialPrice: Double): Double = initialPrice - 5.0
fun discountTenPercent(originalPrice: Double): Double = originalPrice * 0.9

var applyDiscount = ::discountFiveDollars
applyDiscount = ::discountTenPercent

For functions that have multiple parameters, keep in mind that the parameters must match in the same order.

For example, here are two functions. Both return a String. Both accept two parameters - one String and one Double. However, since the order of those parameters doesn’t match, the types of those functions don’t match, so you can’t assign them to the same variable, or else you get an error:

fun menuItemDescription(name: String, price: Double): String =
    "A $name costs $price before discounts and tax."

fun sillyMenuItemDescription(price: Double, name: String): String =
    "You want a $name? It's gonna run you $price, not counting coupons, tax, and whatnot!"

var describeMenuItem = ::menuItemDescription
describeMenuItem = ::sillyMenuItemDescription

Well, we’ve successfully assigned a function to a variable. That can be helpful, but things get even more interesting when we assign a function to a parameter. In other words, we can pass a function as an argument to a function! This is exactly what Bert needed way back in Listing 7.4.

Passing Functions to Functions

Now that we know what function types are, let’s update Bert’s calculateTotal() function. We’ll take Listing 7.4 above and make just a few small changes to it so that it accepts a parameter called applyDiscount that has a function type of (Double) -> Double.

fun calculateTotal(
    initialPrice: Double,
    applyDiscount: (Double) -> Double
): Double {
    // Apply coupon discount
    val priceAfterDiscount = applyDiscount(initialPrice)
    // Apply tax
    val total = priceAfterDiscount * taxMultiplier

    return total
}

With this code in place, Bert can call calculateTotal() with a function reference! Let’s define a few more functions that match the type (Double) -> Double, and then call calculateTotal() with each of them:

fun discountFiveDollars(price: Double): Double = price - 5.0
fun discountTenPercent(price: Double): Double = price * 0.9
fun noDiscount(price: Double): Double = price

val withFiveDollarsOff = calculateTotal(20.0, ::discountFiveDollars) // $16.35
val withTenPercentOff  = calculateTotal(20.0, ::discountTenPercent)  // $19.62
val fullPrice          = calculateTotal(20.0, ::noDiscount)          // $21.80

Yay! Bert is now able to calculate the total for different types of coupons, without needing to create multiple versions of calculateTotal() for each one!

In addition to sending a function into another function, you can also return a function from a function! Let’s look at that next!

Returning Functions from Functions

Instead of typing in the name of the function each time he calls calculateTotal(), Bert would like to just enter the coupon code from the bottom of the coupon that he receives from the customer.

Drawings of the $5 and 10% coupons, pointing out the coupon codes.

To do this, he just needs a function that accepts the coupon code and returns the right discount function. In other words, it’ll have one parameter that’s a String, and its return type will be (Double) -> Double.

A when expression makes this function easy!

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> ::discountFiveDollars
    "TAKE_10"    -> ::discountTenPercent
    else         -> ::noDiscount
}

And, of course, we can update Listing 7.17 to use the new discountForCouponCode() function.

val withFiveDollarsOff = calculateTotal(20.0, discountForCouponCode("FIVE_BUCKS")) // $16.35
val withTenPercentOff  = calculateTotal(20.0, discountForCouponCode("TAKE_10"))    // $19.62
val fullPrice          = calculateTotal(20.0, discountForCouponCode("NONE"))       // $21.80

Functions like calculateTotal() and discountForCouponCode() above, which accept functions as arguments and/or return them as results, are called higher-order functions.

Listing 7.16 annotated - a higher-order function that accepts a function as an argument.funcalculateTotal(initialPrice: Double,applyDiscount: (Double) -> Double): Double {Apply coupon discountvalpriceAfterDiscount = applyDiscount(initialPrice)Apply taxvaltotal = priceAfterDiscount *taxMultiplierreturntotal} Listing 7.18 annotated - a higher-order function that returns a function as a result.fundiscountForCouponCode(couponCode: String): (Double) -> Double =when(couponCode) {"FIVE_BUCKS"-> discountFiveDollars"TAKE_TEN"-> discountTenPercentelse-> noDiscount}

So far, we’ve been able to achieve a lot with function references! They can be quite helpful when you’ve already written the function that you want to reference. But Kotlin also gives us another, more concise way to assign functions to variables and parameters - lambdas!

Introduction to Lambdas

As you might recall from Chapter 1, a literal is when you directly write out a value in your code. For example, in Kotlin, you can create literals for basic types such as String, Int, and Boolean. The highlighted parts of the code below are values that are written as literals.

val string: String = "This is a string"
val integer: Int = 49
val boolean: Boolean = true

Just as you can write literals of Strings, Ints, and Booleans, you can also write a literal of a function!

“Wait,” I can hear you say, “we’ve already been writing functions! How is this any different?” Yes, we’ve been writing named functions with the fun keyword, but we’ve never defined a function directly in an expression, such as on the right-hand side of an assignment, or directly inside a function call.

Annotated code showing that we want to define a function directly in an expression.valapplyDiscount =valwithFiveDollarsOff =calculateTotal(20.0,)

Let’s take another look at the discountFiveDollars() function from earlier in this chapter. We defined that function and then assigned it to a variable by using a function reference. Here’s what it looked like:

fun discountFiveDollars(price: Double) = price - 5.0
val applyDiscount: (Double) -> Double = ::discountFiveDollars

Instead of defining the discountFiveDollars() function with the fun keyword, we can rewrite it as a function literal like this:

val applyDiscount: (Double) -> Double = { price: Double -> price - 5.0 }

The highlighted part of the code above is a function literal. In Kotlin, a function literal written like this is called a lambda. Lambdas are functions, just like the ones we’ve written so far. They’re simply expressed differently. To write a lambda, use an opening brace { and a closing brace } . Then write the parameters before the arrow -> and the body after the arrow.

The various parts of a Kotlin lambda.{ price:  Double -> price -5.0}

Once you’ve assigned a lambda to a variable, you can call it using the variable’s name. Listing 7.21 and Listing 7.22 accomplish the same thing, but the latter does it more concisely.

Traditional Functions vs Lambdas

Both traditional functions and lambdas have parameters and a body, and evaluate to some kind of result. However, unlike traditional functions, the lambda itself does not have a name. Sure, you can choose to assign it to a variable that has a name, but the lambda itself is nameless.

The lambda in Listing 7.22 indicates that the price parameter has a type of Double. Most of the time, however, Kotlin can use its type inference to figure it out. For example, we can rewrite that listing and omit the parameter’s type in the lambda:

val applyDiscount: (Double) -> Double = { price -> price - 5.0 }

Kotlin knows that price must be a Double, because that’s what applyDiscount’s type says it must be. Similarly, the result of the lambda has to match.

Listing 7.23, annotated to show that the type of price and the type of the lambda body are determined by the type of the variable.valapplyDiscount: (Double) -> Double ={ price -> price -5.0}

So, lambdas are a concise way of creating a function right in the middle of an expression. Our lambda above is pretty small already, but we can make it even more concise!

The Implicit it parameter

In cases where there’s only a single parameter for a lambda, you can omit the parameter name and the arrow. When you do this, Kotlin will automatically make the name of the parameter it. Let’s rewrite our lambda to take advantage of this:

val applyDiscount: (Double) -> Double = { it - 5.0 }

The code here is incredibly more concise than the original discountFiveDollars() function!

The implicit it parameter is used often in Kotlin, especially when the lambda is small, like this one. In cases when the lambda is longer, as we’ll see in a moment, it can be a good idea to give the parameter a name explicitly. In future chapters, we’ll also see cases where lambdas are nested inside other lambdas, which is another situation where explicit names are preferred. In many cases, though, the implicit it parameter can make your code easier to read.

Assigning a lambda to a variable can be helpful, but things get even more interesting when we start using lambdas with higher-order functions!

Lambdas and Higher-Order Functions

Passing Lambdas as Arguments

As we learned above, higher-order functions are those that have a function as an input (i.e., parameter) or an output (i.e., the result). Here’s the code from Listing 7.16 and Listing 7.17 above, where we used function references to pass functions as arguments to the calculateTotal() function:

fun calculateTotal(
    initialPrice: Double,
    applyDiscount: (Double) -> Double
): Double {
    // Apply coupon discount
    val priceAfterDiscount = applyDiscount(initialPrice)
    // Apply tax
    val total = priceAfterDiscount * taxMultiplier

    return total
}

fun discountFiveDollars(price: Double) = price - 5.0
fun discountTenPercent(price: Double): Double = price * 0.9
fun noDiscount(price: Double) = price

val withFiveDollarsOff = calculateTotal(20.0, ::discountFiveDollars) // $16.35
val withTenPercentOff  = calculateTotal(20.0, ::discountTenPercent)  // $19.62
val fullPrice          = calculateTotal(20.0, ::noDiscount)          // $21.80

It’s easy to call calculateTotal() with a lambda instead of a function reference. Let’s rewrite the last few lines of the code above to use lambdas. We’ll just take the body from each corresponding function and write it as a lambda instead:

val withFiveDollarsOff = calculateTotal(20.0, { price -> price - 5.0 }) // $16.35
val withTenPercentOff  = calculateTotal(20.0, { price -> price * 0.9 }) // $19.62
val fullPrice          = calculateTotal(20.0, { price })                // $21.80

In cases where function’s last parameter is a function type, you can move the lambda argument outside of the parentheses to the right, like this:

val withFiveDollarsOff = calculateTotal(20.0) { price -> price - 5.0 }
val withTenPercentOff  = calculateTotal(20.0) { price -> price * 0.9 }
val fullPrice          = calculateTotal(20.0) { price -> price }

We’re still sending two arguments to calculateTotal(). The first is inside the parentheses, and the second is outside to the right.

Trailing lambda syntax. First line from Listing 7.27, calling out the first argument (20.0) and the second argument (the lambda).valwithFiveDollarsOff =calculateTotal(20.0) { price -> price -5.0}

In Kotlin, writing the lambda outside of the parentheses like this is called trailing lambda syntax. Regardless of whether you put that last lambda argument inside the parentheses or outside, it works exactly the same. Kotlin developers usually prefer trailing lambdas, though.

Trailing lambda syntax is even more fun when the lambda is the only argument that you’re passing to the function, because then you can omit the parentheses completely!

For example, here’s a higher-order function with a single parameter, which has a function type:

fun printSubtotal(applyDiscount: (Double) -> Double) {
    val result = applyDiscount(20.0)
    val formatted = "$%.2f".format(result)
    println("A $20.00 haircut will cost you $formatted before tax.")
}

When calling printSubtotal(), no parentheses are needed!

printSubtotal { price -> price - 5.0 }
printSubtotal { price -> price * 0.9 }

In addition to using lambdas as arguments, we can also use them as function results!

Returning Lambdas as Function Results

Here’s the code from Listing 7.18 above, where we returned function references:

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> ::discountFiveDollars
    "TAKE_10"    -> ::discountTenPercent
    else         -> ::noDiscount
}

We can very easily replace these function references with lambdas, just as we did for the function arguments in Listing 7.27.

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> { price -> price - 5.0 }
    "TAKE_10"    -> { price -> price * 0.9 }
    else         -> { price -> price }
}

Lambdas with Multiple Statements

So far, the lambdas that we’ve used have contained only one simple expression each.

Sometimes you need a lambda that has multiple statements in it. To do this, simply put each statement on a separate line, as you would inside any other function. Unlike a regular function, though, you won’t use the return keyword to return your result. Instead, the very last line of the lambda will be the result of the call.

For example, we might want to print some debugging information inside our lambda that calculates the five-dollars-off coupon:

val withFiveDollarsOff = calculateTotal(20.0) { price ->
    val result = price - 5.0
    println("Initial price: $price")
    println("Discounted price: $result")
    result
}

When we’ve got a lambda that spans multiple lines like this, it’s conventional to put the parameters and arrow on the same line as the opening brace, as seen above. Here’s the same code, with some callouts indicating each part.

Listing 7.32, annotated to point out the parameter, arrow, multiple statements, and the fact that there's no return keyword.valwithFiveDollarsOff =calculateTotal(20.0) { price ->valdiscountedPrice = price -5.0println("Initial price:$price")println("Discounted price:$discountedPrice")discountedPrice}

Before we wrap up this chapter, we’ve got one more concept to cover - closures!

Closures

Bert’s salon is doing great now. He’s easily able to calculate the customers’ totals, even when they have different coupons. Let’s take a look at his code, including calculateTotal(), discountForCouponCode(), and how he ends up calling them to get the total.

fun calculateTotal(
    initialPrice: Double,
    applyDiscount: (Double) -> Double
): Double {
    // Apply coupon discount
    val priceAfterDiscount = applyDiscount(initialPrice)
    // Apply tax
    val total = priceAfterDiscount * taxMultiplier

    return total
}

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> { price -> price - 5.0 }
    "TAKE_10"    -> { price -> price * 0.9 }
    else         -> { price -> price }
}

val initialPrice = 20.0
val couponDiscount = discountForCouponCode("FIVE_BUCKS")
val total = calculateTotal(initialPrice, couponDiscount)

Bert noticed that when he introduces a new coupon, he needs to write another lambda. For example, if he adds a new coupon for nine dollars off, and another one for fifteen percent off, he would need to write a few more lambdas, like this:

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> { price -> price - 5.0 }
    "NINE_BUCKS" -> { price -> price - 9.0 }
    "TAKE_10"    -> { price -> price * 0.9 }
    "TAKE_15"    -> { price -> price * 0.85 }
    else         -> { price -> price }
}

That’s not too bad, actually, but he decided he could make one last small improvement. There are really two main categories of coupons - dollar amount and percentages.

Drawings of dollar amount coupons: $5 off and $9 off... and percentage coupons: 10% off and 15% off.

He wrote these two functions to match the two categories of coupons that he identified:

fun dollarAmountDiscount(dollarsOff: Double): (Double) -> Double =
    { price -> price - dollarsOff }

fun percentageDiscount(percentageOff: Double): (Double) -> Double {
    val multiplier = 1.0 - percentageOff
    return { price -> price * multiplier }
}

It’s important to note that these two functions do not calculate the discount themselves. Instead, they create functions that calculate the discount. This is a little easier to see in percentageDiscount(), where we’re using an explicit return keyword rather than an expression body.

Another neat thing here is that these lambdas use variables that are defined outside of the lambda body. The first one uses the dollarsOff variable (a parameter of the wrapping function), and the second uses the multiplier variable. When a lambda uses a variable that’s defined outside of its body like this, it’s sometimes referred to as a closure.

Now, creating a new coupon is easy. Instead of writing the lambda inline in discountForCouponCode(), Bert can just call either dollarAmountDiscount() or percentageDiscount() to create the lambda for him.

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> dollarAmountDiscount(5.0)
    "NINE_BUCKS" -> dollarAmountDiscount(9.0)
    "TAKE_10"    -> percentageDiscount(0.10)
    "TAKE_15"    -> percentageDiscount(0.15)
    else         -> { price -> price }
}

Summary

Congratulations! Lambdas can be a tough concept for many programmers who haven’t used them before. If you still feel a little unsure about them, it’s completely fine. We’ll use them a lot in upcoming chapters, and you’ll get more familiar with them as you go.

In this chapter, you learned about:

Throughout this chapter, we’ve seen some simple use cases for lambdas, but they really shine when used with collections. In the next chapter, we’ll introduce collections, and we’ll see how we can use lambdas to do all sorts of fun things with them!

Thanks to James Lorenzen for reviewing this chapter!


  1. Note, however, that you cannot use named arguments when you call a function using the variable’s name. [return]

Share this article:

  • Share on Twitter
  • Share on Facebook
  • Share on Reddit