DEV Community

Shalveena
Shalveena

Posted on

What are Lambdas and Higher Order Functions in Kotlin and How Can You Use Them?

Lambdas are widely used in Kotlin so you've most likely come across them, but if you're like me you might be asking, "What exactly is a Lambda? What does it do and how can I use them?"

This article attempts to answer those questions. Specifically:

  • What a lambda is
  • How to write a lambda (in other words, the syntax of a lambda)
  • What a higher order function is and how lambdas are used in higher order functions.

So, what is a lamda?

The simple answer is that lambdas are functions. But more specifically, a lambda is a function that is written as an expression, and that is not declared using the fun keyword.

Here is an example of a regular function:

fun greeting() {
  println("Hello World!")
}
Enter fullscreen mode Exit fullscreen mode

The same function can be written as a lambda:

val greeting = { println("Hello World!")}
Enter fullscreen mode Exit fullscreen mode

You can call the lambda function by using the variable name followed by parentheses: greeting().

val greeting = { println("Hello World!") }

// In the main function:
greeting() // prints: Hello World!
Enter fullscreen mode Exit fullscreen mode

Note: you can also call the lambda function by using greeting.invoke()

Great, now that you know a bit about what a lambda is, we will have a look at how to write (and read) a lambda in the next section.

How to write a lambda? The syntax.

You can write a lambda within curly braces. The parameters are separated from the function body by an arrow. So the parameters are written first, then an arrow and then the function body.

Extending our greeting example, let's give it one parameter so you can see the syntax of a lambda with parameters:

val greeting = { name: String -> println("Hello, $name")}

// In the main function:
greeting("Sam") // prints: Hello, Sam
Enter fullscreen mode Exit fullscreen mode

Function Types

Since Kotlin is a statically typed language, every variable must have a type. In the above example, the greeting variable is of a function type because it holds a function (since a lambda is a function after all). Variables that hold lambdas are of a function type.

Kotlin can infer the function type implicitly, but you can also write it explicitly.

If you were to write out the full syntax of greeting, it would look like:

val greeting: (String) -> Unit = { name: String -> println("Hello, $name")}
Enter fullscreen mode Exit fullscreen mode

The above reads as:

  • greeting is a variable of a function type
  • it will hold a function
  • the function it holds will take a String argument and return Unit.

Short-hand syntax for lambdas

There are three simplifications we can make to the lambda syntax, which makes it easier to read.

1) When the variable declaration has an explicitly declared function type, for example a function type that takes a String and returns Unit (like in the example above), you don't have to explicitly define the parameter of the lambda as being of String type.
This is because Kotlin already knows that the lambda will have a String type parameter since it has been explicitly defined in the variable declaration. This is easier to illustrate through code:

val greeting: (String) -> Unit = { name: String -> println("Hello, $name")}

/* We don't need to explicitly say that the
name parameter will be of String type.
We can simplify it to:
*/

val greeting: (String) -> Unit = { name -> println("Hello, $name")}
Enter fullscreen mode Exit fullscreen mode

2) If the lambda has only one parameter, you don't have to write the parameter. Instead, there is an it keyword that you can use to refer to the parameter. So you can further simplify greeting to:

val greeting: (String) -> Unit = { println("Hello! $it")}
Enter fullscreen mode Exit fullscreen mode

3) You don't need to use the return keyword. This is because the last expression within the lambda's body is treated as the return value. To illustrate, let's change our greeting function to return a String:

val greeting: (String) -> String = {"Hello, $it"}
greeting("Sam") // Will return "Hello, Sam" even though the return keyword was not used in greeting.
Enter fullscreen mode Exit fullscreen mode

What is a higher order function?

Higher order functions are functions and take a function as an argument. Phew, so many "functions" there!

Note: When writing higher order functions, it is best practice in Kotlin to have the function argument as the last parameter.

An example of a higher order function:

fun createLongGreeting (firstSentence: String, secondSentence: String, operation: (String) -> String) : String {
    return "${operation(firstSentence)} $secondSentence"
}
Enter fullscreen mode Exit fullscreen mode

In the above example, createLongGreeting is a higher order function that creates and returns a string template using:

  • the result of calling an operation (function) on the first sentence, and
  • the second sentence

It takes 3 arguments:

  1. A string (the firstSentence parameter)
  2. A string (the secondSentence parameter)
  3. A function (the operation parameter)

Okay, so how can lambdas help you when it comes to higher order functions?

Since higher order functions take another function as an argument, you need to be able to pass functions to them. If you try to pass a regular function as the argument, Kotlin will throw an error because it will think you're trying to call the function instead of simply passing it in as an argument. For example if you try the following, it will give you an error:

fun partGreeting(name: String): String {
  return "${name}! It's has been a long time!"
}

createLongGreeting("Sam", "How are you?", partGreeting) // error: Function invocation 'partGreeting(...)' expected. No value passed for parameter 'name'
Enter fullscreen mode Exit fullscreen mode

You need a way to easily pass functions as arguments to the higher order functions. And that's where lambdas come to the rescue.

Passing in a lambda as a parameter to a higher order function

Lambdas allow you to easily pass functions as arguments.

Continuing with the example from above, you can pass a lambda directly into createLongGreeting as its last argument. The lambda must be one that takes a String as it's argument and returns a String.

// The createLongGreeting function
fun createLongGreeting (firstSentence: String, secondSentence: String, operation: (String) -> String) : String {
    return "${operation(firstSentence)} $secondSentence"
}

// In the main function:
// Calling createLongGreeting and passing a lambda directly to it as it's last argument.
val firstGreeting = createLongGreeting("Sam", "How are you?", { name -> "$name! It's been a long time!" })

println(firstGreeting) // Sam! It's been a long time! How are you?
Enter fullscreen mode Exit fullscreen mode

Here is a breakdown of what is happening in the above example:

  1. We call createLongGreeting, passing it:
    • "Sam" as it's first argument
    • "How are you?" as it's second argument
    • A lambda as it's third argument
  2. createLongGreeting calls our lambda, passing it "Sam"
  3. Our lambda returns a string: "Sam! It's been a longtime!"
  4. createLongGreeting creates a string template, inserting the string returned from the lambda call ("Sam! It's been a longtime!") and the string we passed as the second argument ("How are you?")
  5. createLongGreeting returns the string it created in step 4: "Sam! It's been a long time! How are you?"

Trailing lambda syntax

To make our example easier to read, we can actually put the lambda outside the parenthesis:

val firstGreeting = createLongGreeting("Sam", "How are you?") { name -> "$name! It's been a long time!" }
Enter fullscreen mode Exit fullscreen mode

This is known as a trailing lambda syntax.

Assigning a lambda to a variable and passing that variable to the higher order function

Another way we can use lambdas with higher order functions is by assigning the lambda to a variable and passing that variable as an argument to the higher function.

First, we need to make a lambda and assign it to a variable:

val greeting: (String) -> String = {"Hello, $it!"}
Enter fullscreen mode Exit fullscreen mode

Next, we pass greeting to createLongGreeting. Note that we don't call greeting directly. We just pass it to createLongGreeting. It's createLongGreeting that calls greeting when making its string template.

// The createLongGreeting function:
fun createLongGreeting (firstSentence: String, secondSentence: String, operation: (String) -> String) : String {
    return "${operation(firstSentence)} $secondSentence"
}

// Our lambda - the greeting function:
val greeting: (String) -> String = {"Hello, $it!"}

// In the main function, call createLongGreeting and pass it greeting as it's last argument:
val secondGreeting = createLongGreeting("Sam", "How are you?", greeting)
println(secondGreeting) // Hello, Sam! How are you?
Enter fullscreen mode Exit fullscreen mode

As mentioned earlier, if greeting were a regular function (or a single line function) instead of a lambda, we could not pass it to createLongGreeting as above, because Kotlin will think that we are trying to call greeting and give an error. But there is a way to pass a regular function as an argument: we can use the :: symbol to pass the function as a reference.

In the example below, greeting is written as a single line function and passsed to createLongGreeting as a reference:

fun greeting(name: String) = "Hello, $name!"

// In the main function:
val secondGreeting = createLongGreeting("Sam", "How are you?", ::greeting)
Enter fullscreen mode Exit fullscreen mode

Bonus tip: you could capture a user's input, clean it up and use it in createLongGreeting:

First, create a function to clean the user's input data:


val cleanUserInput: (String) -> String = { userInput -> userInput.replace(userInput[0], userInput[0].uppercaseChar()).trim()
}
Enter fullscreen mode Exit fullscreen mode

Then, in the main function, prompt the user to enter their name and assign it to a variable:

print("Please enter your name: ")
val usersName = readln()
Enter fullscreen mode Exit fullscreen mode

Finally, call createLongGreeting, passing it the input from the user, the string "Good to see you!" and the cleanUserInput function. The cleanedUserInput function will be called within createLongGreeting to clean the input from the user:

val thirdGreeting = createLongGreeting(usersName, "Good to see you!", cleanUserInput)
println(thirdGreeting) // if the user inputs "hallie", the output would be "Hallie Good to see you!"
Enter fullscreen mode Exit fullscreen mode

So there you have it - now you know more about:

  • what lambdas are,
  • how they look like,
  • how to write them,
  • what higher order functions are,
  • and how you can use a lambda to pass a function as an argument to a higher order function.

Thanks for reading!

Top comments (1)

Collapse
 
mitch_ profile image
mitch

Pro tip for those who are just leaning: You are going to want to get really smart with how you use this new knowledge but try to keep things simple. Dont pass a function as an argument unless its really solving a problem that is impossible otherwise. Being "clever" is almost always results in less readable code.