Lambdas

function types

Kotlin support function types. Values of that types can be anonymous functions, lambdas or reference to the class method.

To declare function type you list types of parameters in parentheses and after arrow specify return type like (A, B) -> C.

The parameter types list may be empty, as in () -> A.

Use Unit type when no return value like (A, B) -> Unit.

Optionally, parameters can be named: (x: Int, y: Int) -> Point.

// create alias
typealias ClickHandler = (Button, ClickEvent) -> Unit

// assign reference to the method of current class
var clickHandler: (Button, ClickEvent) -> Unit = ::onClick;

// assign reference to the method of object
var clickHandler: (Button, ClickEvent) -> Unit = view::onClick;

// assign anonimous function
var clickHandler: (Button, ClickEvent) -> Unit = fun (bt: Button, event: ClickEvent){
    // do something
}

// assign lambda expression
// last value is used as return value
val l : (Int, String) -> String = {a: Int, s: String ->
            val b = a*a
            s+" "+b 
        }

// in lambda types of parameters can be omitted
val l2 : (Int, String) -> String = {a, s ->
            val b = a*a
            s+" "+b 
        }

// You cannot use the return keyword to exit a lambda.
// To do this, define a labeled lambda and use a qualified return.
val lambda = testLambda@ { message: String, name: String -> 
    if(message.length < 3) return@testLambda
    
    println("$message for $name")
}        

lambda expression

Lambdas are always declared in curly braces. At begin you specify list of parameters, then the arrow then the body of lambda.

It is allowed not to declare the only one parameter and omit ->. In this case the parameter will be implicitly declared under the name it.

You can explicitly return a value from the lambda using the qualified return syntax. The qualifier can be the name of a lambda label or the name of a function with a lambda parameter.

The value of the last expression is implicitly returned, unless the return type is Unit.

The unqualified return is used to exit from an outer function. If the outer function does not exist, the compiler throws an error.

// filter method accept (it: Int) -> Boolean argument
intElements.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
}

intElements.filter {
    val shouldFilter = it > 0
    shouldFilter
}

val lambda = testLambda@ { message: String, name: String -> 
    if(message.length < 3) return@testLambda
    
    println("$message for $name")
}    

val lambda = testLambda@ { message: String, name: String -> 
    if(message.length < 3) return // ERROR
    
    println("$message for $name")
}    

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // returns from hasZeros
    }
    return false
}

higher-order function

A higher-order function is a function that takes functions as parameters, or returns a function.

You can call lambdas via the invoke() method or operator ()

fun filter(f : (it: Int) -> Boolean){
    // .. do something
    val b = f.invoke(34)
    val c = f(34)
}

// calling filter function
filter({it -> it%2==0 }) // with default name "it"
filter({ anotherName -> // with custom name "anotherName"
    anotherName%2==0
})

Last lambda argument can be placed outside the parentheses. Such syntax is also known as trailing lambda.

// calling filter function with trailing lambda
filter(){
    it%2==0 
}

If the lambda is the only argument to that call, the parentheses can be omitted entirely.

filter { it%2==0 }

receiver type

Function types can optionally have an additional receiver type, which is specified before the dot in the notation: the type A.(B) -> C represents functions that can be called on a receiver object A with a parameter B and return a value C. Function literals with receiver are often used along with these types.

Inside the body of the function literal, the receiver object passed to a call becomes an implicit this, so that you can access the members of that receiver object without any additional qualifiers, or access the receiver object using a this expression.

One of the most important examples of receiver usage is type-safe builders.

class Person {
    var firstName: String = ""
    var lastName: String = ""
}

// with receiver type
fun person(lambda: Person.() -> Unit) = Person().apply(lambda)
// without receiver type
fun person2(lambda: (Person) -> Unit) = Person().apply(lambda)

// usage example
val p1 = person {
    firstName = "Harry"
}

// in this case we need to use it.
val p2 = person2 {
    it.firstName = "Harry"
}

closure

A lambda expression / anonymous function / local function / object expression can access its closure, which includes the variables declared in the outer scope. The variables captured in the closure can be modified.

inline

The inline modifier allows to inline the function itself and the lambdas passed to it into the call site. It is useful because closures cause memory allocations and virtual calls introduce runtime overhead.

// lock is inline function
lock(l) { foo() } 

// this call will generate following code
l.lock()
try {
    foo()
} finally {
    l.unlock()
}

noinline allows you to specify which lambda argument should not be inline.

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }

Inlining may cause the generated code to grow; however, if you do it in a reasonable way (avoiding inlining large functions), it will pay off in performance, especially at "megamorphic" call-sites inside loops.

return value

The return keyword inside a lambda means returning from the nearest outer function (not outer lambda expressions). This is only allowed for inline lambda expressions.

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // returns from hasZeros
    }
    return false
}

To exit a lambda, use a label.

arrayOf(1,2,3,4,5,6,7,8,9).forEach {
    if (it == 5) {
        // return from lambda, i.e. from current forEach iteration.
        // In our case this is equivalent to the continue keyword.
        return@forEach
    }
    
    print(" $it") 
}
// result:
// 1 2 3 4 6 7 8 9
// 5 not will be printed

You can assign the own label name.

arrayOf(1,2,3,4,5,6,7,8,9).forEach mylabel@{
    if (it == 5) {
        return@mylabel
    }
    println(it)
}

There is no direct equivalent for break, but it can be simulated by adding another nesting lambda and non-locally returning from it:

fun myFunction() {
    run mylabel@{
        arrayOf(1,2,3,4,5,6,7,8,9).forEach{
            if (it == 5) {
                return@mylabel
            }
            println(it)
        }
    }
}
// result:
// 1 2 3 4

functional interface

You can declare a single abstract method (SAM) interface as a functional interface. This allows you to convert the interface to a lambda and vice versa.

fun interface ExampleInterface {
    fun myMethod(arg: Int): String
}

// You can use this interface as lambda (Int) -> String and vice versa.