# Local Functions

You can define functions anywhere—even inside other functions.

Named functions defined within other functions are called local functions. Local functions reduce duplication by extracting repetitive code. At the same time, they are only visible within the surrounding function, so they don’t “pollute your namespace.” Here, even though log() is defined just like any other function, it’s nested inside main():

// LocalFunctions/LocalFunctions.kt
import atomictest.eq

fun main() {
  val logMsg = StringBuilder()
  fun log(message: String) =
    logMsg.appendLine(message)
  log("Starting computation")
  val x = 42  // Imitate computation
  log("Computation result: $x")
  logMsg.toString() eq """
    Starting computation
    Computation result: 42
  """
}

Local functions are closures: they capture vars or vals from the surrounding environment that would otherwise have to be passed as additional parameters. log() uses logMsg, which is defined in its outer scope. This way, you don’t repeatedly pass logMsg into log().

You can create local extension functions:

// LocalFunctions/LocalExtensions.kt
import atomictest.eq

fun main() {
  fun String.exclaim() = "$this!"
  "Hello".exclaim() eq "Hello!"
  "Hallo".exclaim() eq "Hallo!"
  "Bonjour".exclaim() eq "Bonjour!"
  "Ciao".exclaim() eq "Ciao!"
}

exclaim() is available only inside main().

Here is a demonstration class and example values for use in this atom:

// LocalFunctions/Session.kt
package localfunctions

class Session(
  val title: String,
  val speaker: String
)

val sessions = listOf(Session(
  "Kotlin Coroutines", "Roman Elizarov"))

val favoriteSpeakers = setOf("Roman Elizarov")

You can refer to a local function using a function reference:

// LocalFunctions/LocalFunctionReference.kt
import localfunctions.*
import atomictest.eq

fun main() {
  fun interesting(session: Session): Boolean {
    if (session.title.contains("Kotlin") &&
      session.speaker in favoriteSpeakers) {
      return true
    }
    // ... more checks
    return false
  }
  sessions.any(::interesting) eq true
}

interesting() is only used once, so we might be inclined to define it as a lambda. As you will see later in this atom, the return expressions within interesting() complicate the task of turning it into a lambda. We can avoid this complication with an anonymous function. Like local functions, anonymous functions are defined within other functions—however, an anonymous function has no name. Anonymous functions are conceptually similar to lambdas but use the fun keyword. Here’s LocalFunctionReference.kt rewritten using an anonymous function:

// LocalFunctions/InterestingSessions.kt
import localfunctions.*
import atomictest.eq

fun main() {
  sessions.any(
    fun(session: Session): Boolean {    // [1]
      if (session.title.contains("Kotlin") &&
        session.speaker in favoriteSpeakers) {
        return true
      }
      // ... more checks
      return false
    }) eq true
}
  • [1] An anonymous function looks like a regular function without a function name. Here, the anonymous function is passed as an argument to sessions.any().

If a lambda becomes too complicated and hard to read, replace it with a local function or an anonymous function.

# Labels

Here, forEach() acts upon a lambda containing a return:

// LocalFunctions/ReturnFromFun.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4, 5)
  val value = 3
  var result = ""
  list.forEach {
    result += "$it"
    if (it == value) {
      result eq "123"
      return                   // [1]
    }
  }
  result eq "Never gets here"  // [2]
}

A return expression exits a function defined using fun (that is, not a lambda). In line [1] this means returning from main(). Line [2] is never called and you see no output.

To return only from a lambda, and not from the surrounding function, use a labeled return:

// LocalFunctions/LabeledReturn.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4, 5)
  val value = 3
  var result = ""
  list.forEach {
    result += "$it"
    if (it == value) return@forEach
  }
  result eq "12345"
}

Here, the label is the name of the function that called the lambda. The labeled return expression return@forEach tells it to return only to the name forEach.

You can create a label by preceeding the lambda with label@, where label can be any name:

// LocalFunctions/CustomLabel.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4, 5)
  val value = 3
  var result = ""
  list.forEach tag@{             // [1]
    result += "$it"
    if (it == value) return@tag  // [2]
  }
  result eq "12345"
}
  • [1] This lambda is labeled tag.
  • [2] return@tag returns from the lambda, not from main().

Let’s replace the anonymous function in InterestingSessions.kt with a lambda:

// LocalFunctions/ReturnInsideLambda.kt
import localfunctions.*
import atomictest.eq

fun main() {
  sessions.any { session ->
    if (session.title.contains("Kotlin") &&
      session.speaker in favoriteSpeakers) {
      return@any true
    }
    // ... more checks
    false
  } eq true
}

We must return to a label so it exits only the lambda and not main().

# Manipulating Local Functions

You can store a lambda or an anonymous function in a var or val, then use that identifier to call the function. To store a local function, use a function reference (see [Member References](javascript:void(0))).

In the following example, first() creates an anonymous function, second() uses a lambda, and third() returns a reference to a local function. fourth() achieves the same effect as third() but uses a more compact expression body. fifth() produces the same effect using a lambda:

// LocalFunctions/ReturningFunc.kt
package localfunctions
import atomictest.eq

fun first(): (Int) -> Int {
  val func = fun(i: Int) = i + 1
  func(1) eq 2
  return func
}

fun second(): (String) -> String {
  val func2 = { s: String -> "$s!" }
  func2("abc") eq "abc!"
  return func2
}

fun third(): () -> String {
  fun greet() = "Hi!"
  return ::greet
}

fun fourth() = fun() = "Hi!"

fun fifth() = { "Hi!" }

fun main() {
  val funRef1: (Int) -> Int = first()
  val funRef2: (String) -> String = second()
  val funRef3: () -> String = third()
  val funRef4: () -> String = fourth()
  val funRef5: () -> String = fifth()

  funRef1(42) eq 43
  funRef2("xyz") eq "xyz!"
  funRef3() eq "Hi!"
  funRef4() eq "Hi!"
  funRef5() eq "Hi!"

  first()(42) eq 43
  second()("xyz") eq "xyz!"
  third()() eq "Hi!"
  fourth()() eq "Hi!"
  fifth()() eq "Hi!"
}

main() first verifies that calling each function does indeed return a function reference of the expected type. Each funRef is then called with an appropriate argument. Finally, each function is called and then the returned function reference is immediately called by adding an appropriate argument list. For example, calling first() returns a function, so we call that function by appending the argument list (42).

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