Lambdas

Lambdas

Overview

Lambda expressions in full, play a very important role in the day-to-day development cycle of any Kotliner. This article gets you familiarised with what they are and how to use them to better enhance your developer experience.

Definition

A lambda expression is a function literal . But what is a function literal? It is a function that is not declared but it is passed immediately as an expression.

// Declared
fun add(a: Int, b: Int) = a + b

// Lambda
val sum = { a: Int, b:Int -> a + b }

In the example above add is a declared and named function while sum is assigned to an expression that is a function literal a.k.a lambda. But what is the syntax of a lambda expression?

Syntax

val isEven : (Int) -> Boolean = { number: Int -> 
    // body
    number % 2 == 0
}

The lambda expression in the above code is enclosed with curly braces {} .

  • After the curly braces, you declare a parameter

  • The body of the expression comes after ->

  • The last line of the expression will be treated as the return value.

NOTE: If the last line is not a value the expression will not return any value and the return type will be of Unit. Let's write a few examples...

Examples

  • Full Syntax (all types declared)

      val addTwo : (Int, Int) -> Int = { a:Int, b:Int -> a + b }
      println(addTwo(1, 2))
      // 3
      println(addTwo.invoke(3,4)) // you can also call it using invoke
      // 7
    
  • Omitting type declarations

      val isEven = { a: Int -> a % 2 == 0 }
      println(isEven(3))
      // false
    
  • Without return type

      val sayGreeting = { name: String -> println("Hello $name") }
      println(sayGreeting("Kotlin"))
      // Hello Kotlinkotlin.Unit
    

    In the function above it prints Hello Kotlin but also prints the return value of the function which is Unit

  • As a function parameter

      fun String.takeIfOrNull (block : (String) -> Boolean) : String? {
          return if(block(this)) this else null
      }
      println(
          "Hello".takeIfOrNull({ value -> value.length > 3 })
      )
      // Hello
    

    In the function above it prints Hello since the passed lambda expression returns true. But we can write the call for takeIfOrNull even simpler. How?

  • Trailing lambdas

      println(
          "Hello".takeIfOrNull { value -> value.length > 3 }
      )
      // Hello
    

    If the last parameter of a function is a function then the passed lambda can be placed outside the brackets. If the function takes only a function then the brackets can be completely omitted like in the example above. Could we remove more boilerplate? YES!

  • Accessing parameter values

      println(
          "Hello".takeIfOrNull { it.length > 3 }
      )
      // Hello
    

    If there is only one parameter the compiler can help us omit the parameter declaration and the arrow (->). We can thus access the parameter under the name it.

Caveats

  • Implicit parameter 'it' of enclosing lambda is shadowed

    Sometimes you might have a lambda inside another lambda, when this happens it's good to specify the parameter name to avoid shadowing it.

      // Before
      listOf(1,2,3).map { 
          it.toString() + listOf(3,5,7).map { it.plus(1) } 
      }
    
      // After
      listOf(1,2,3).map { 
          it.toString() + listOf(3,5,7).map { value -> value.plus(1) } 
      }
    
  • Unused variables

    In some cases the expression can have more than one parameter but you only want to use one. You can replace the name with an underscore to show it's unused.

      // Before
      listOf(Pair(true, 1),Pair(true, 2)).map { (bool, number) -> 
          number + 1 
      }
    
      // After
      listOf(Pair(true, 1),Pair(true, 2)).map { (_, number) -> 
          number + 1 
      }
    
  • Returning values

    In other cases, you may want to specify the return value of a lambda expression. In such cases using the return keyword would suffice but ensure you specify the name of the lambda to avoid exiting the parent function call.

      // Before
      listOf(Pair(true, 1),Pair(true, 2)).map { (bool, number) -> 
          if(bool) return number
          val value = ...
          ...
          return value     
      }
    
      // After
      listOf(Pair(true, 1),Pair(true, 2)).map { (bool, number) -> 
          if(bool) return@map number
          val value = ...
          ...
          return@map value     
      }
    

Conclusion

With that you can try the inbuilt lambdas provided by Kotlin or create your own, just remember the caveats. Leave a comment down below with your thoughts and what you'd like to know next. See you in the next article!