# Lambdas

Lambdas produce compact code that’s easier to understand.

A lambda (also called a function literal) is a low-ceremony function: it has no name, requires a minimal amount of code to create, and you can insert it directly into other code.

As a starting point, consider map(), which works with collections like List. The parameter for map() is a transformation function which is applied to each element in a collection. map() returns a new List containing all the transformed elements. Here, we transform each List item to a String surrounded with []:

// Lambdas/BasicLambda.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ n: Int -> "[$n]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

The lambda is the code within the curly braces used in the initialization of result. The parameter list is separated from the function body by an arrow -> (the same arrow used in when expressions).

The function body can be one or more expressions. The final expression becomes the return value of the lambda.

BasicLambda.kt shows the full lambda syntax, but this can often be simplified. We typically create and use a lambda in place, which means Kotlin can usually infer type information. Here, the type of n is inferred:

// Lambdas/LambdaTypeInference.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ n -> "[$n]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

Kotlin can tell n is an Int because the lambda is being used with a List<Int>.

If there’s only a single parameter, Kotlin generates the name it for that parameter, which means we no longer need the n ->:

// Lambdas/LambdaIt.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ "[$it]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

map() works with a List of any type. Here, Kotlin infers the type of the lambda argument it to be Char:

// Lambdas/Mapping.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'd')
  val result =
    list.map({ "[${it.toUpperCase()}]" })
  result eq listOf("[A]", "[B]", "[C]", "[D]")
}

If the lambda is the only function argument, or the last argument, you can remove the parentheses around the curly braces, producing cleaner syntax:

// Lambdas/OmittingParentheses.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'd')
  val result =
    list.map { "[${it.toUpperCase()}]" }
  result eq listOf("[A]", "[B]", "[C]", "[D]")
}

If the function takes more than one argument, all except the last lambda argument must be in parentheses. For example, you can specify the last argument for joinToString() as a lambda. The lambda is used to transform each element to a String, then all the elements are joined:

// Lambdas/JoinToString.kt
import atomictest.eq

fun main() {
  val list = listOf(9, 11, 23, 32)
  list.joinToString(" ") { "[$it]" } eq
    "[9] [11] [23] [32]"
}

If you want to provide the lambda as a named argument, you must place the lambda inside the parentheses of the argument list:

// Lambdas/LambdaAndNamedArgs.kt
import atomictest.eq

fun main() {
  val list = listOf(9, 11, 23, 32)
  list.joinToString(
    separator = " ",
    transform = { "[$it]" }
  ) eq "[9] [11] [23] [32]"
}

Here’s the syntax for a lambda with more than one parameter:

// Lambdas/TwoArgLambda.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.mapIndexed { index, element ->
    "[$index: $element]"
  } eq listOf("[0: a]", "[1: b]", "[2: c]")
}

This uses the mapIndexed() library function, which takes each element in list and produces the index of that element together with the element. The lambda that we apply after mapIndexed() requires two arguments to match the index and the element (which is a character, in the case of List<Char>).

If you aren’t using a particular argument, you can ignore it using an underscore to eliminate compiler warnings about unused identifiers:

// Lambdas/Underscore.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.mapIndexed { index, _ ->
    "[$index]"
  } eq listOf("[0]", "[1]", "[2]")
}

Note that Underscore.kt can be rewritten using list.indices:

// Lambdas/ListIndicesMap.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.indices.map {
    "[$it]"
  } eq listOf("[0]", "[1]", "[2]")
}

Lambdas can have zero parameters, in which case you can leave the arrow for emphasis, but the Kotlin style guide recommends omitting the arrow:

// Lambdas/ZeroArguments.kt
import atomictest.*

fun main() {
  run { -> trace("A Lambda") }
  run { trace("Without args") }
  trace eq """
    A Lambda
    Without args
  """
}

The standard library run() simply calls its lambda argument.

  • -

You can use a lambda anywhere you use a regular function, but if the lambda becomes too complex it’s often better to define a named function, for clarity, even if you’re only going to use it once.

Exercises and solutions can be found at www.AtomicKotlin.com.