🚗 · Function types & literals ⚙️

10 min read · Updated on by

Learn about higher-order functions, function types, function literals, anonymous functions, lambdas, closures, lexical scope, function references and the difference between anonymous functions and lambdas.

In Kotlin, functions are first-class, which means they can be treated the same way as any other value type.

This means:

  • They can be assigned to variables
  • They can be passed as arguments to, and returned from, other functions. Functions accepting or returning other functions are called higher-order functions
  • They have a type

Function types are denoted using e.g.(Int, String) -> String. If a function does not return a value, which is the same as saying it returns Unit, the type is e.g. (Int, String) -> Unit.

If a function does not accept parameters, it’s type is e.g. () -> String.

The function type can be instantiated using two different literals:

  • the anonymous function
  • the lambda function

Kotlin has closures, same as Java — a function can access the lexical scope it was defined it. It’s easier to understand when demonstrated:


//sampleStart
fun main() {
    var x = 0

    fun printX() {
        println(x)
    }
    x++
    
    // This will print 1. The definition of
    // printX() keeps a >reference< to all
    // variables defined in the same lexical 
    // scope - i.e. it can 'see' x
    printX()

    
    // This will not compile - y is defined
    // >after< printY, so it's not part of
    // the lexical scope.
    fun printY() {
        println(y)
    }
    var y = 0
}
//sampleEnd

Now, I’ll be honest — some of the stuff in the following examples can be a little hardcore for people not versed in this style of writing code. It is by no means necessary to be able to write this kind of code to be able to use Kotlin, although you can build some really amazing stuff using these techniques.

In any case, I've included these hardcore bits for educational purposes, to show what can be done, and what the syntax permits, and to offer it as a voluntary exercise to those who wish to stretch their brain muscles. Remember, the easy stuff is all you need to know.

Anonymous functions

Anonymous functions are written in the same way as normal function definitions are, but without the name.


//sampleStart
// This part after the equals sign is an anonymous function. 
// It's a value, and must be assigned to a variable if you want to use it.
val myFun: (Int) -> Unit = fun(a: Int) {
  println(a)
}

// Type inference applies as usual, so we don't have to specify the type 
// if we don't want to.
val myFun2 = fun(a: Int) {
  println(a)
}

// Anonymous functions can be returned and expression syntax can be used
// You can also name the parameters in the type signature, for better 
// readability
// This is a function which returns an "increment by 5" function
fun add5Fun(): (increasedByFive: Int) -> Int {
    return fun(num: Int): Int = num + 5
}

// Here, the return type can be omitted, since we're defining addXFun 
// as an expression
fun addXFun(x: Int) = fun(num: Int): Int { return num + x }

// This is the same as above (a function that returns a function),
// but assigned to a variable and with types explicitly written out. 
// Take your time them, this can be complicated when you're not used 
// to it (and even when you are).
// Let the type of the variable guide you - this is a function that
// accepts an Int, and returns another function. The returned
// function accepts an Int, and returns an Int. 
val addXFun2: (Int) -> ((Int) -> Int) = fun(x: Int): (Int) -> Int {
  return fun(num: Int): Int { return num + x }
}

val add5 = addXFun(5) // (Int) -> Int
val `7` = add5(2) // Int
val `also 7` = addXFun(5)(2) // Int

// Just so you know, function types in signatures are right
// associative (in case you want to omit parentheses in type 
// signatures)
val addXFun3: (Int) -> (Int) -> Int = fun(x: Int): (Int) -> Int {
  return fun(num: Int): Int { return num + x }
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, you're the secret weapon we adore,
        With your type safety, coding's never a chore.
        You're the superstar, the shining light,
        In the universe of languages, you're so right!
    """.trimIndent()
    println(poem)
}

Lambda functions

  • Lambda functions are written as a pair of curly braces. Parameters are placed after the opening brace, separated by a comma (with optional types) and followed by ->
  • The last expression in a lambda is returned. Lambda’s can span multiple lines.
  • If a lambda only has a single parameter, the parameter list can be omitted, and the parameter referenced by the name it
  • When passing a lambda as the last argument to a function, the lambda is moved outside the function call. This sentence will probably make no sense when you read it, so just see the examples bellow.

//sampleStart
val sum: (Int, Int) -> Int = {a, b -> a + b}
// We can omit types as long as the compiler has enough information to infer them
val sum2 = { a: Int, b: Int -> a + b}

// This won't work. Also, unlike anonymous functions, there is no way to explicitly
// specify the return type of a lambda.
// val sum3 = {a, b -> a + b}
// No way to make this work, since we can't specify the return type
// val sum4 = {a: Int, b -> a + b}

// This works
val addTwo = {a: Int -> a + 2 }
// Single parameter can be omitted, but we need to make sure the compiler can infer its type
val addTwo2: (Int) -> Int = { it + 2 }

// You can write cool stuff when you can accept functions as
// parameters (or return them). BTW, functions that accept/return
// other functions are called 'Higher Order Functions' - just
// in case you ever run into that term.
fun map(list: List<Int>, transform: (Int) -> Int): List<Int> {
    val result = mutableListOf<Int>()
    for (element in list) {
        result.add(transform(element))
    }
    return result
}

// This works, but it's not how you do it
val increments1 = map(listOf(1, 2, 3), { it + 1 })

// This is how you do it. Notice the how 
// the lambda moved outside of the 
// parameter list
val increments2 = map(listOf(1, 2, 3)) { 
  it + 1 
}

/*
 * If a function only takes a lambda as an argument
 * the parenthesis can be omitted completely (as shown
 * in makeOdd3)
 */

fun funWithIncrement(function: (Int) -> Int): (Int) -> Int = { function(it) + 1 }

// This works, but it's not how you do it
val makeOdd1 = funWithIncrement({ 2 * it })

// This also works, but it's also not how you do it
val makeOdd2 = funWithIncrement() {
  2 * it 
}

// This is how you do it. Holy moly, it almost
// looks like we added a new keyword to the language! 
val makeOdd3 = funWithIncrement {
  2 * it 
} 

// Rewrite of the examples used in the 'anonymous functions' part, using lambdas
val addXFun_lambda: (Int) -> (Int) -> Int = { x -> { num ->  num + x } }
val addXFun_lambda2: (Int) -> (Int) -> Int = { x -> { it + x } }
val addXFun_lambda3: (Int) -> (Int) -> Int = { { num -> num + it } }
// This is probably the way you would actually do it
val addXFun_lambda4 = { x: Int -> { num: Int ->  num + x } }
//sampleEnd
fun main() {
    val poem = """
        From lambdas to DSLs, Kotlin's got it all,
        With its clean syntax, it stands tall.
        In the realm of development, it's our go-to mate,
        For coding adventures, it's never too late!
    """.trimIndent()
    println(poem)
}

How to reference functions

Function literals are referenced by the name of the variable they were assigned to.

Functions or methods that were defined by a statement can be referenced using ::.


//sampleStart
// Remember - this is a function literal (on the rhs)...
val myFun = fun(a: Int) = a + 1
val myFun2 = myFun

// ...and this is a function statement (defined using expression syntax)
fun myFun3(a: Int) = a + 1
val myFun4 = ::myFun // (Int) -> Int

fun executeOnThree(f: (Int) -> Int) = f(3)
val resultOfThree = executeOnThree(myFun)
val alsoResultOfThree = executeOnThree(::myFun3)

// Type must be specified explicitly, because println() has multiple overloaded variants
val myFun5: (Any) -> Unit = ::println

// Member functions are referenced by prepending the class name
val myFun6 = String::length // (String) -> Int
val lengthOfAbc = myFun6("Abc") // 3

// Instance functions are referenced by prepending the variable or the literal. The result is called 
// a bound callable reference
val myString = "Hello World"
val myFun7 = myString::length // () -> Int
val myFun8 = "Hello"::length // () -> Int
val resultOf7 = myFun7() // 11
val resultOf8 = myFun8() // 5
//sampleEnd
fun main() {
    val poem = """
        Kotlin's here to make your coding dreams bloom,
        With concise code and safety, there's no gloom.
        No more boilerplate, just pure delight,
        In the world of coding, Kotlin takes flight!
    """.trimIndent()
    println(poem)
}

Anonymous functions vs lambdas

You might be asking why Kotlin has two ways of declaring a function literal. The main reason is that, while the syntax of lambdas is more lightweight, it does not easily permit specifying the return type (basically the only thing you can do is an explicit cast). In 99% of the cases, this is not needed, since the type of the result is inferred, but in the small number of cases where that isn’t possible, you can use anonymous functions which allow you to specify the return type, as seen above.

There is another reason that has to do with non-local returns and return-at-labels. We think it is a good idea to be aware that things like that exist, but we don’t think you’ll be needing it very often. If you ever do, come back and study this more thoroughly.

Exercises

Anonymous functions

Implement the following as anonymous functions (not lambdas!).


import org.junit.Assert
import org.junit.Test

class TestAnonymousFunctions() {

    @Test(timeout = 1000)
    fun testAnonymousFunctions() {
        Assert.assertEquals(
            "Implementation of executeOnInt is incorrect",
            12,
            executeOnInt(10) { it + 2 }
        )

        Assert.assertEquals(
            "Implementation of passThreeAsFirstArgument is incorrect",
            10,
            passThreeAsFirstArgument(7) { one, two -> one + two }
        )

        Assert.assertEquals(
            "Implementation of addOne is incorrect",
            11,
            addOne(10)
        )

        Assert.assertEquals(
            "Implementation of add5Fun is incorrect",
            6,
            add5Fun()(1)
        )

        Assert.assertEquals(
            "Implementation of addXFun is incorrect",
            6,
            addXFun(5)(1)
        )

        Assert.assertEquals(
            "Implementation of addXFun2 is incorrect",
            6,
            getAddXFun2()(5)(1)
        )

        Assert.assertEquals(
            "Implementation of passThreeASFirstArgument is incorrect",
            13,
            passThreeAsFirstArgument(10, Int::plus)
        )

        Assert.assertEquals(
            "Implementation of lift is incorrect",
            listOf(2, 3, 4),
            lift(fun(a: Int): Int = a + 1)(listOf(1, 2, 3))
        )
    }
}

//sampleStart
/**
 * Implement the following as anonymous functions (not lambdas!)
 */

fun executeOnInt(input: Int, operation: (Int) -> Int): Int = TODO("Implement executeOnInt!")

fun addOne(input: Int): Int = executeOnInt(input, TODO("Implement addOne!"))
fun passThreeAsFirstArgument(input: Int, operation: (Int, Int) -> Int): Int = executeOnInt(input, TODO("Implement passThreeAsFirstArgument!"))

fun add5Fun(): (Int) -> Int = TODO("Implement add5Fun!")
fun addXFun(x: Int): (Int) -> Int = TODO("Implement addXFun!")

// addXFun2 is wrapped in a function for technical reasons involving a bug in the Kotlin
// Playground. Just ignore getAddXFun2, and focus on implementing addXFun2.
fun getAddXFun2(): (Int) -> ((Int) -> Int) {
    
	val addXFun2: (Int) -> ((Int) -> Int) = TODO("Implement addXFun2!")
    
	return addXFun2
}

// Takes a function operating on Ints and "lifts" ("upgrades") it to the same
// function operating on lists of Ints.
//
// Example usage:
//
// val addOne: (Int) -> Int = fun(a: Int): Int = a + 1
// f(1) // 2
//
// val liftedAddOne = lift(addOne)
// liftedAddOne(listOf(1, 2, 3)) //  listOf(2, 3, 4)
// 
// Read up on https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map.html
// 
// Hint: The return type is a function, so start by writing the function
// definition of the returned function. Once the parameters of this function
// are writen down, think about how you can use 'f' and 'map' to transform
// them to the desired output. You can do it, but it will take time - be patient
// and don't give up!
fun lift(f: (Int) -> Int): (List<Int>) -> List<Int> = TODO("Implement lift!")
//sampleEnd

Solution

Lambda functions

Implement the following as lambdas (not anonymous functions!)


import org.junit.Assert
import org.junit.Test

class TestLambdaFunctions() {

    @Test(timeout = 1000)
    fun testLambdaFunctions() {
        Assert.assertEquals(
            "Implementation of executeOnIntLambda is incorrect",
            12,
            executeOnIntLambda(10) { it + 2 }
        )

        Assert.assertEquals(
            "Implementation of addOneLambda is incorrect",
            11,
            addOneLambda(10)
        )

        Assert.assertEquals(
            "Implementation of add5FunLambda is incorrect",
            6,
            add5FunLambda()(1)
        )

        Assert.assertEquals(
            "Implementation of addXFunLambda is incorrect",
            6,
            addXFunLambda(5)(1)
        )

        Assert.assertEquals(
            "Implementation of addXFun2Lambda is incorrect",
            6,
            getAddXFun2Lambda()(5)(1)
        )

        Assert.assertEquals(
            "Implementation of passThreeAsFirstArgumentLambda is incorrect",
            13,
            passThreeAsFirstArgumentLambda(10, Int::plus)
        )

        Assert.assertEquals(
            "Implementation of liftLambda is incorrect",
            listOf(2, 3, 4),
            liftLambda(fun(a: Int): Int = a + 1)(listOf(1, 2, 3))
        )
    }
}

//sampleStart
/**
 * Implement the following as anonymous functions (not lambdas!)
 */

fun executeOnIntLambda(input: Int, operation: (Int) -> Int): Int = operation(input)

fun addOneLambda(input: Int): Int = executeOnIntLambda(input, TODO("Implement addOneLambda!"))
fun passThreeAsFirstArgumentLambda(input: Int, operation: (Int, Int) -> Int): Int = executeOnIntLambda(input, TODO("Implement passThreeAsFirstArgumentLambda!"))

fun add5FunLambda(): (Int) -> Int = TODO("Implement add5FunLambda!")
fun addXFunLambda(x: Int): (Int) -> Int = TODO("Implement addXFunLambda!")

// addXFun2 is wrapped in a function for technical reasons involving a bug in the Kotlin
// Playground. Just ignore getAddXFun2Lambda, and focus on implementing addXFun2Lambda.
fun getAddXFun2Lambda(): (Int) -> ((Int) -> Int) {
    
	val addXFun2Lambda: (Int) -> ((Int) -> Int) = TODO("Implement addXFun2Lambda!")
    
	return addXFun2Lambda
}

// Takes a function operating on Ints and "lifts" ("upgrades") it to the same
// function operating on lists of Ints.
//
// Example usage:
//
// val addOne: (Int) -> Int = fun(a: Int): Int = a + 1
// f(1) // 2
//
// val liftedAddOne = liftLambda(addOne)
// liftedAddOne(listOf(1, 2, 3)) //  listOf(2, 3, 4)
// 
// Read up on https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map.html
// 
// Hint: The return type is a function, so start by writing the function
// definition of the returned function. Once the parameters of this function
// are writen down, think about how you can use 'f' and 'map' to transform
// them to the desired output. You can do it, but it will take time - be patient
// and don't give up!
fun liftLambda(f: (Int) -> Int): (List<Int>) -> List<Int> = TODO("Implement lift!")
//sampleEnd

Solution

2 thoughts on “🚗 · Function types & literals ⚙️”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer