A complete guide to Kotlin lambda expressions

Lambdas Expressions

As a first-class construct, functions are also data types, so you can store functions in variables, pass them to functions, and return them from functions.

Lambdas Expressions are essentially anonymous functions that we can treat as values — we can, for example, pass them as arguments to functions, return them, or do any other thing we could do with a normal object.

Lambda expression syntax

The full syntactic form of lambda expressions is as follows:

val sum: (Int, Int) -> Int 
= { x: Int, y: Int -> x + y }

That syntax includes the following rules:

  • A lambda expression is always surrounded by curly braces.
  • Parameter declarations in the full syntactic form go inside curly braces and may have optional type annotations.
  • Parameter declarations and the lambda body must be separated
    by a -> and the body goes after the ->.
  • If the inferred return type of the lambda is not Unit, the last expression inside the lambda body is treated as the return value.
  • The return value may be inferred from the lambda body
  • If there is a single parameter, it may be accessed within the lambda body using an implicit it reference.

If you leave all the optional annotations out, what’s left looks like this:

val sum = { x: Int, y: Int -> x + y }

Let’s look at some code that illustrates this lambda expression syntax.

Example 1: a function with no parameter and return nothing:-

val lambda1 : () -> Unit 
= { println("Hello") }

In this case, lambda1 is a function that takes no arguments and returns Unit. Because there are no argument types to declare, and the return value may be inferred from the lambda body, we may simplify this lambda even further (as below).

val lambda2 = { println("Hello") }

The Unit return is inferred by the fact that the last expression of the lambda body, the call to println(), returns Unit.

val lambda1 : () -> Unit = { println("Hello") }
// Case 1: full syntactic form

val lambda2 = { println("Hello") }
// Case 2: declare type of parameter on right side,
// but we have no parameter in this case

fun main()
{
lambda1()
lambda2()
}

Output:
Hello
Hello

Example 2: a function with one parameter and return nothing:-

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

It includes all optional type information. The name parameters include their explicit type information. The variable (lambda3) also explicitly defines the type information for the function expressed by the lambda.

We may simplify this lambda even further (as below).



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

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

In the lambda4 example, the type information is inferred from the lambda itself. The parameter values are explicitly annotated with the String type while the final expression can be inferred to return Unit.

For lambda5, the variable (lambda5) includes the type information. Because of this, the parameter declarations of the lambda can omit the explicit type annotations; name will be inferred as String types.

val lambda3 : (String) -> Unit 
= { name: String -> println("Hello $name") }
// case 1: full syntactic form

val lambda4 = { name: String -> println("Hello $name") }
// case 2: declare type of parameter on right side

val lambda5 : (String) -> Unit
= { name -> println("Hello $name") }
// case 3: declare type of parameter on left side

val lambda6 : (String) -> Unit = { println("Hello $it") }
// case 4: Uses of it if we have single parameter


fun main()
{
lambda3("Gaurav Tyagi")
lambda4("Gaurav Tyagi")
lambda5("Gaurav Tyagi")
lambda6("Gaurav Tyagi")
}

Output:
Hello Gaurav Tyagi
Hello Gaurav Tyagi
Hello Gaurav Tyagi
Hello Gaurav Tyagi

Example 3: a function with two parameters and return some value:-

val lambda7 : (String, String) -> String 
= { first: String, last: String ->
"My name is $first $last"
}

It includes all optional type information. Both the first and last parameters include their explicit type information. The variable (lambda7) also explicitly defines the type information for the function expressed by the lambda.

We may simplify this lambda even further (as below).

val lambda8 = { first: String, last: String -> 
"My name is $first $last"
}
val lambda9 : (String, String) -> String = { first, last ->
var fullName = first+" "+last
"My name is $fullName"
}

In the lambda8 example, the type information is inferred from the lambda itself. The parameter values are explicitly annotated with the String type while the final expression can be inferred to return aString.

For lambda9, the variable (lambda9) includes the type information. Because of this, the parameter declarations of the lambda can omit the explicit type annotations; first and last will both be inferred as String types.

val lambda7 : (String, String) -> String 
= { first: String, last: String ->
"My name is $first $last"
}
// case 1: full syntactic form

val lambda8 = { first: String, last: String ->
"My name is $first $last"
}
// case 2: declare type of parameter on right side

val lambda9 : (String, String) -> String = { first , last ->
var fullName = first+" "+last
"My name is $fullName"
}
// case 3: declare type of parameter on left side


fun main()
{
var name1 = lambda7("Gaurav", "Tyagi")
println(name1)
var name2 =lambda8("Gaurav", "Tyagi")
println(name2)
var name3 =lambda9("Gaurav", "Tyagi")
println(name3)
}

Output:
My name is Gaurav Tyagi
My name is Gaurav Tyagi
My name is Gaurav Tyagi

Functional (SAM) interfaces

An interface with only one abstract method is called a functional interface, or a Single Abstract Method (SAM) interface. The functional interface can have several non-abstract members but only one abstract member.

To declare a functional interface in Kotlin, use the fun modifier.

fun interface KRunnable {
fun invoke()
}

Traditional way:

interface Greeterlistener {
fun greet(item: String)
}

fun greetMe(name: String, greeter: Greeterlistener) {
greeter.greet(name)
}

fun main() {
// Creating an instance of a class
greetMe("Gaurav", object : Greeterlistener {
override fun greet(item: String) {
println("Hello $item")
}
})
}

Output:
Hello Gaurav

The greetMe function takes an instance of a GreeterListener interface. To satisfy the need, we create an anonymous class to implement Greeter and define our greet behavior.

With SAM conversion, we can simplify this.

fun interface Greeterlistener {
fun greet(item: String)
}

fun greetMe(name: String, greeter: Greeterlistener) {
greeter.greet(name)
}


fun main() {

greetMe("Gaurav", {
println("Hello $it")
})

greetMe("Gaurav") {
println("Hello $it")
}

}

Output:
Hello Gaurav
Hello Gaurav

Notice that the lambda here is now performing SAM conversion to represent the GreeterListener type.

Notice also the change to the GreeterListener interface. We added the fun keyword to the interface. This marks the interface as a functional interface that will give a compiler error if you try to add more than one public abstract method.

If you’re creating an interface with a single public, abstract method, consider making it a functional interface so you may leverage lambdas when working with the type.

another example of SAM with returning String:

fun interface Greeterlistener {
fun greet(item: String): String
}

fun greetMe(name: String, greeter: Greeterlistener) : Int {
var res = greeter.greet(name)
return res.length
}

fun main() {

var result = greetMe("Gaurav", {
println("Hello $it")
"Hi $it"
})
println(result)

result = greetMe("Gaurav") {
println("Hello $it")
"Hi $it"
}
println(result)
}

Output:
Hello Gaurav
9
Hello Gaurav
9

another example to understand SAM:

consider the following Kotlin functional interface:

fun interface IntPredicate {
fun accept(i: Int): Boolean
}

If you don’t use a SAM conversion, you will need to write code like this:

// Creating an instance of a class
val isEven : IntPredicate = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}

By leveraging Kotlin’s SAM conversion, you can write the following equivalent code instead:

// Creating an instance using lambda
val isEven : IntPredicate = IntPredicate { it % 2 == 0 }

A short lambda expression replaces all the unnecessary code.

fun interface IntPredicate {
fun accept(i: Int): Boolean
}

val isEven : IntPredicate = IntPredicate { it % 2 == 0 }

fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}

Output:
Is 7 even? - false

Type aliases

You can also simply rewrite the above using a type alias for a functional type:

typealias IntPredicate = (i: Int) -> Boolean

val isEven: IntPredicate = { it % 2 == 0 }

fun main() {
println("Is 7 even? - ${isEven(7)}")
}

Output:
Is 7 even? - false

Returning values from a lambda

In the previous section, We demonstrated that the return value of a lambda is provided by the last expression within the lambda body. This is true whether returning a meaningful value or when returning Unit.

But what if you want to have multiple return statements within your lambda expression?

Implementation of multiple returns within a lambda expression.

val lambda = { greeting: String, name: String -> 
if(greeting.length < 3) return // error: return not allowed here

println("$greeting $name")
}

with lambda expressions, adding a return in this way results in a compiler error.

To accomplish the desired result, we must use qualified return (as below).

val lambda = greet@ { greeting: String, name: String -> 
if(greeting.length < 3) return@greet

println("$greeting $name")
}

here, we’ve labeled our lambda by adding greet@ before the first curly brace. Now we can reference this label and use it to return (return@greet) from our lambda to the outer, calling function.

The above example doesn’t return any meaningful value as return type for this case is Unit. What if we wanted to return a String rather than printing a String?

val lambda = greet@ { greeting: String, name: String -> 
if(greeting.length < 3) return@greet ""
if(greeting.length < 6) return@greet "Welcome!"

"$greeting $name"
}

Notice that while we now have multiple return statements, we still do not use an explicit return for our final value. This is important. If we added a return to our final line of the lambda expression body, we would get a compiler error. The final return value must always be implicitly returned.

another example to return from lambda:

fun calculateSum1() {
val numbers = listOf(1, 2, 3, 4, 5)
var sum = 0
numbers.forEach { number ->
if (number == 3) {
return@forEach
// Exit the lambda and continue with the next iteration
}
sum += number
}
println("Sum: $sum")
}

fun calculateSum2() {
val numbers = listOf(1, 2, 3, 4, 5)
var sum = 0
numbers.forEach greet@{ number ->
if (number == 3) {
return@greet
// Exit the lambda and continue with the next iteration
}
sum += number
}
println("Sum: $sum")
}

fun main() {
calculateSum1()
calculateSum2()
}

Output:
Sum: 12
Sum: 12

forEach build-in function in kotlin is as:

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}

Ignore the parameters

If we want to ignore one, or more parameters, we must rely on the underscore naming convention.

val lambda10: (String, String) -> Unit = { _, _ -> 
println("Hello there!")
}

val lambda11: (String, String) -> Unit = { _, last ->
println("Hello $last")
}

val lambda12: (String) -> Unit = { _ ->
println("Hello there!")
}

val lambda13: (String) -> Unit = {
println("Hello there!")
}
// lambda13 is because of it only

fun main() {
lambda10("Gaurav", "Tyagi")
lambda11("Gaurav", "Tyagi")
lambda12("Gaurav")
lambda13("Gaurav")
}

Output:
Hello there!
Hello Tyagi
Hello there!
Hello there!

Working with Build-In Functional Interfaces

Supplier: A Supplier is used when you want to generate or supply values without taking any input.

// Build-In 
public interface Supplier<T> {
T get();
}
import java.util.function.*

private fun useOfSupplier(expr : Supplier<ArrayList<Int>>) {
var result: ArrayList<Int> = expr.get()
println(result) // Output: [1, 2, 3]
}

private fun testSupplier() {
var list : ArrayList<Int> = ArrayList<Int>()
list.add(1)
list.add(2)
list.add(3)
// return something but accept nothing
useOfSupplier { list }
}

fun main() {
testSupplier()
}

Consumer: A Consumer is used when you want to do something
with a parameter but not return anything.

// Build-In
public interface Consumer<T> {
void accept(T t);
}
import java.util.function.*

private fun useOfConsumer(expr : Consumer<String>) {
expr.accept("Gaurav")
}

private fun testConsumer() {
// accept something but return nothing
useOfConsumer {
println(it) // Output: Gaurav
}
}

fun main() {
testConsumer()
}

Predicate: Predicate is often used when filtering or matching. It accept a single parameter and return Boolean.

// Build-In
public interface Predicate<T> {
boolean test(T t);
}

import java.util.function.*

private fun useOfPredicate(list : ArrayList<Int>, expr : Predicate<Int>) : ArrayList<Int> {
val newList : ArrayList<Int> = ArrayList<Int> ()
for(i in 0 until list.size) {
if(expr.test(list[i])) {
newList.add(list[i])
}
}
return newList
}

private fun testPredicate() {
var list : ArrayList<Int> = ArrayList<Int>()
list.add(1)
list.add(2)
list.add(3)
list.add(4)
// accept something but return Boolean
var result = useOfPredicate(list) {
it % 2 == 0
}

println("$result") // Output: [2, 4]

// accept something but return Boolean
result = useOfPredicate(list) { item ->
item % 2 == 0
}

println("$result") // Output: [2, 4]
}

fun main() {
testPredicate()
}

another example to understand Predicate by using user defined Predicate:

here internal kotlin function “filter” uses the something same as Predicate

// User Defined
fun interface Predicate<String> {
fun accept(str: String): Boolean
}

fun queries(todoList: ArrayList<String>, expr : Predicate<String>) : ArrayList<String> {
var result : ArrayList<String> = ArrayList<String>()
for (i in 0..todoList.size-1) {
if(expr.accept(todoList[i])){
result.add(todoList[i])
}
}
return result
}

fun main() {

val names = ArrayList<String>()
names.add("Gaurav")
names.add("Garv")
names.add("GauravTyagi")
var evenName = queries(names,{ it.length % 2 == 0 })
println(evenName)

evenName = queries(names){ it.length % 2 == 0 }
println(evenName)

val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers)
}
Output:
[Gaurav, Garv]
[Gaurav, Garv]
[2, 4]

BiFunction: BiFunction is a Build-In function which accept 2 arguments and produces a result.

// Build-In
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}

import java.util.function.*


private fun useOfBiFunction(expr : BiFunction<Int,Char,String>) {
var result = expr.apply(2,'E')
println("$result") // Output: 2 , E as a String
}


private fun testBiFunction() {
useOfBiFunction { v1, v2 ->
"$v1 , $v2"
}
}

fun main() {
testBiFunction()
}

1 thought on “A complete guide to Kotlin lambda expressions”

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top