Lambda expressions are an important concept in Kotlin’s functional programming. In order to master functional programming, one must master lambda expressions and its various writing and implementation, which are the basis for mastering functional programming.

Lambda basic form

Lambda expressions have three characteristics:

  1. Lambda expressions exist in {}
  2. The parameters and parameter types (omitted) are to the left of ->
  3. The body of the function is to the right of ->

The return value of a lambda expression always returns the value of the last expression inside the function body

These three forms of lambda expressions must be well understood in order to further understand Kotlin and functional programming.

There is no parameter

The form without parameters is:

Val function name = {function body}

Example:

Val hello = {println("hello kotlin")} // Equivalent function fun hello() {println("hello kotlin")}Copy the code

A parameter

  1. Full expression:

Val function name: (parameter 1 type, parameter 2 type,…) -> Return value type = {parameter 1, parameter 2… -> function body}

  1. Expression return value type can be automatically inferred form

Val function name = {parameter 1: type 1, parameter 2: type 2,… -> function body}

Example:

Val sum: (Int, Int) -> Int = {a, b -> a + b} val sum = {a: Int, b: Int -> a + b} Int): Int { return a + b }Copy the code

When there is only one parameter, the parameter parameter in the return value can be omitted, and the reference is made through IT

There are two ways to call a lambda, either through () or through the invoke() function. There is no difference between the two ways.

fun main(args: Array<String>) {
    val lambda = { println("test") }
    lambda()
    lambda.invoke()
}Copy the code

When using a lambda expression, you can use an underscore (_) to indicate an unused argument, indicating that the argument is not processed.

Anonymous functions

The form of the anonymous function is:

Val function name = fun(argument 1: type 1, argument 2: type 2,…) : return value type {function body}

Example:

Sum (a: Int, b: Int): Int {return a + b} (a: Int, b: Int): Int {return a + b})Copy the code

The evolution of higher order functions

A higher order function is actually a mathematical concept of a composite function, f(g(x)).

Reference function

fun cal(a: Int, b: Int, f: (c: Int, d: Int) -> Int): Int {
    return f(a, b)
}

fun sum(a: Int, b: Int): Int {
    return a + b
}

fun main(args: Array<String>) {
    val result = cal(2, 3, ::sum)
    println("result = $result")
    // result = 8
}Copy the code

The last argument in the CAL function is f: (a: Int, b: Int) -> Int, which means that the argument is a function reference, and the function body calls the function to which the last argument refers. Then we define the sum function, which is the third argument to the CAL function.

::sum is a reference to the sum function. The statement CAL (2, 3, ::sum) is equivalent to executing sum(2, 3), so the output is 5.

Function references can further simplify function calls, as shown in the following example:

class Test { fun doSomething() { println("test") } fun doTest(f: (Test) -> Unit) { f(this) } } fun main(args: Array<String>) {val t = Test() {t. dotest {Test -> test.dosomething ()} // Using a reference function (Test::doSomething is actually a simplification of the lambda expression {Test -> test.dosomething ()}) t.dotest (Test::doSomething)}Copy the code

Parameter lambda

fun cal(a: Int, b: Int, f: (a: Int, b: Int) -> Int): Int {
    return f(a, b)
}

fun main(args: Array<String>) {
    val sum = { a: Int, b: Int -> a + b }
    val result = cal(2, 3, sum)
    println("result = $result")
    // result = 5
}Copy the code

Write a function as a lambda and assign it directly to the CAL function as an argument.

Further, you can omit the lambda variable and simply pass the lambda expression into the function.

fun cal(a: Int, b: Int, f: (a: Int, b: Int) -> Int): Int {
    return f(a, b)
}

fun main(args: Array<String>) {
    val result = cal(2, 3, { a: Int, b: Int -> a + b })
    println("result = $result")
    // result = 5
}Copy the code

In addition, when calling higher-order functions in Kotlin, you can write the lambda expression outside if the last argument is a lambda expression, and you can omit the parentheses if there are no other arguments.

fun cal(a: Int, b: Int, f: (a: Int, b: Int) -> Int): Int { return f(a, b) } fun main(args: Array<String>) {val result = CAL (2, 3, {a: Int, b: Int -> a + b})} Int -> a + b } println("result = $result") // result = 5 }Copy the code

The function variables

fun main(args: Array) { val sumLambda = {a: Int, b: Int -> a + b} var numFun: (a: Int, b: Int) -> Int numFun = {a: Int, b: Int -> a + b} sumLambda numFun = ::sum numFun(1,2)}Copy the code

You can see that this variable can be equal to a lambda expression, another lambda expression variable, or an ordinary function, but you need to add (::) before the function name to get a function reference.

Lambda expression instances

Let’s take a look at some concrete lambda expressions using a simple example.

Val sum = fun(a: Int, b: Int): Int {return a + b} val sum = fun(a: Int, b: Int): {return a + b} Int highSum(a: Int, b: Int, f: (Int, Int) -> Int): Int {return f(a, b)} fun main(args: Sum val add = sum(1, 2) println(add) highSum val add2 = highSum(3, 3) HighSum val add3 = highSum(5, 6) {a, b -> a + b} highSum val add3 = highSum(5, 6) ::namedSum) println(add3) // the forEach argument accepts a function args.forEach({it: String -> println(it)}); Args. forEach({it -> println(it)}); // The lambda expression can be moved out in the last argument Args.foreach () {println(it)}Copy the code

Function types and instantiations

For data types such as Int and String, the type of the function is:

(Type1, Type2, ...) Fun test(a: Int, f: (Int) -> Int): Int {return f(a)}Copy the code

(Int) -> Int; (Int) -> Int;

For example, Int instance 1, String instance “xys”, then obtain the instance of the function, the main customer through the following three ways:

  1. The :: double colon operator denotes a reference to a function
  2. Lambda expressions
  3. Anonymous functions
Fun main(args: Array<String>) {// reference function println(test(1, 2, ::add)) // anonymous function val add = fun(a: Int, b: Int): Int {return a + b} println(test(3, 4, add)) Println (test(5, 6) {a, b -> a + b})} fun test(a: Int, b: Int, f: Int) (Int, Int) -> Int): Int { return f(a, b) } fun add(a: Int, b: Int): Int { return a + b }Copy the code

The type of a lambda expression

The following example gives you an idea of the type of a lambda expression. The code is shown below.

// No parameter, mandatory String ()-> String // Two integer arguments, return a String (Int, Int) -> String // A lambda expression and an integer are passed, return Int (()->Unit, Int) -> IntCopy the code

Lambda expressions can be typed in a form similar to the above, but like Int and String, lambda expressions have their own class, called Function.

Kotlin encapsulates Function0 to Function22, a total of 23 Function types, representing the number of parameters from 0 to 22.

The return of a lambda expression

Unless the return point is specified using the tag, return is returned from the most recent function declared with the fun keyword.

fun main(args: Array<String>) {
    var sum: (Int) -> Unit = tag@{
        print("Test return $it")
        return@tag
    }
    sum(3)
}Copy the code

SAM conversion

SAM = Single Abstract Method

The SAM transformation is designed to invoke a syntactic sugar provided by Java code in Kotlin code, which is a lambda-like implementation of Java’s single method interface, such as the most common Android view.setonClickListener:

// SAM convert in KT view.setOnClickListener{view -> doSomething} // Java interface public interface OnClickListener{void onClick(View v); }Copy the code

SAM conversions are syntactic sugar specifically provided for Java to convert lambda expressions into instances of the corresponding anonymous class. To implement the same functionality in Kotlin, you just need to use function arguments.

A lambda expression with a receiver

Lambda expressions actually come in two forms, the basic one described earlier and the one with a receiver, and the two lambda expressions are shown below.

Normal lambda expressions: {() -> R}

That is, the function takes no arguments and returns a value of type R.

Lambda expressions with receivers: {T.() -> R}

That is, declare a receiver object of type T with no input and return a value of type R.

The extension function in Kotlin is actually a lambda expression with a receiver,

The main difference between a receiver lambda and a normal lambda is the orientation of this. This in T.() -> R represents an instance of T itself, while this in () -> R represents an instance of the outer class.

Use TypeAlias to alias repeated lambda expressions

fun fun1(f: (Int) -> Unit) { f(1) } fun fun2(f: (Int) -> Unit) {f(2)} // Use typeAlias TypeAlias intFun = (Int) -> Unit fun fun3(f: intFun) {f(3)} fun fun4(f: intFun) { f(4) } fun main(args: Array<String>) { fun1 { println(it) } fun2 { println(it) } fun3 { println(it) } fun4 { println(it) } }Copy the code

closure

If a function declares or returns a function internally, the function is called a closure.

Variables inside a function can be accessed and modified by the declared function inside the function, which allows the closure to carry state (all intermediate values are stored in memory).

You can use closures to make functions have state, thereby encapsulating the state of functions and making them object oriented.

Why do you need closures

Before we can understand closures, we need to understand the scope of variables. In Kotlin, there are only two kinds of scope of variables, namely global variables and local variables.

  • Global variables, both inside and outside the function, can be accessed directly.
  • Local variables, accessible only from within the function.

How to access local variables inside a function from outside the function is through closures, which are designed to allow developers to read variables inside a function.

So closures are functions that can read local variables of other functions.

Closures allow functions to carry state

fun test(): () -> Int {
    var a = 1
    println(a)
    return fun(): Int {
        a++
        println(a)
        return a
    }
}

fun main(args: Array<String>) {
    val t = test()
    t()
    t()
}

// output
1
2
3Copy the code

The type of the variable t is actually an anonymous function, so when the function t is called, it is actually executing the returned anonymous function, and since the closure can carry the value of the enclosing variable, the state value of a is passed down.

Closures can access variables outside of the function body. This is called variable capture. Closures carry state by holding the captured variables in a special area of memory.

Kotlin implements interface callbacks

Single method callback

class Test {
    private var callBack: ((str: String) -> Unit)? = null
    fun setCallback(myCallBack: ((str: String) -> Unit)) {
        this.callBack = myCallBack
    }
}Copy the code

The implementation of the interface is replaced by a function.

Evolution of callback writing

The Kotlin way of writing Java ideas

interface ICallback { fun onSuccess(msg: String) fun onFail(msg: String) } class TestCallback { var myCallback: ICallback? = null fun setCallback(callback: ICallback) { myCallback = callback } fun init() { myCallback? .onSuccess("success message") } } fun main(args: Array<String>) { val testCallback = TestCallback() testCallback.setCallback(object : ICallback { override fun onSuccess(msg: String) { println("success $msg") } override fun onFail(msg: String) { println("fail $msg") } }) testCallback.init() }Copy the code

Use lambda expressions instead of anonymous inner class implementations.

class TestCallback {

    var mySuccessCallback: (String) -> Unit? = {}
    var myFailCallback: (String) -> Unit? = {}

    fun setCallback(successCallback: (String) -> Unit, failCallback: (String) -> Unit) {
        mySuccessCallback = successCallback
        myFailCallback = failCallback
    }

    fun init() {
        mySuccessCallback("success message")
        myFailCallback("fail message")
    }
}

fun main(args: Array<String>) {
    val testCallback = TestCallback()
    testCallback.setCallback({ println("success $it") }, { println("fail $it") })
    testCallback.init()
}Copy the code

This removes interfaces and anonymous inner classes.

Usage scenarios for higher-order functions

An important use case for higher-order functions is the operation of sets. The following example uses Java and Kotlin to implement the “find the maximum” method.

data class Test(val name: String, val age: Int) fun main(args: Array<String>) {val testList = listOf(Test("xys", 18), Test("qwe", 12), Test("rty", 10), Test(" ZXC ", 10); 2)) findMax(testList) // Kotlin println(testlist.maxby {it.age}) println(testlist.maxby (Test::age))} fun findMax(test: List<Test>) { var max = 0 var currentMax: Test? = null for (t in test) { if (t.age > max) { max = t.age currentMax = t } } println(currentMax) }Copy the code

Functional set operations

fliter & map

Filter is used to filter data, as are filterIndexed, which is a filter with an Index, and filterNot, which filters all data that does not meet the criteria.

Map is used to transform data and represents a one-to-one transformation relationship that can do one transformation on data in a collection, as is mapIndexed().

fun main(args: Array<String>) { val test = listOf(1, 3, 5, 7, 9) // The filter function iterates through the set and selects those elements that return true when applied to a given lambda. Println (" numbers greater than 5 ${test.filter {it > 5}}") // Println (" ${test.map {it * it}}") val testList = listOf(test ("xys", 18), ${test.map {it * it}}") Test("qwe", 12), Test("rty", 10), Test("zxc", ${testlist.map {it.name}}") println(" ${testlist.map {it.name}}") println(" ${testList ${testList.filter { it.age > 10 }.map { it.name }}") } data class Test(val name: String, val age: Int)Copy the code

all & any & count & find

fun main(args: Array<String>) { val test = listOf(1, 3, 5, 7, Println (" whether all = >10 ${test.all {it >10}}") println(" whether all = >10 ${test.all {it >10}}") println(" whether all = >10 ${test Println (" ${test.count {it > 5}}") ${test.count {it > 5}}" ${test.findlast {it > 5}} ${test.findlast {it > 5}} ${test.findlast {it > 5}} ${test.findlast {it > 5}} ${test.findlast {it > 5}}Copy the code

groupBy & partition & flatMap

FlatMap () represents a one-to-many relationship that transforms each element into a new set, which is then tiled into a set.

The groupBy() method returns a Map> object where Key is the condition for grouping and value is the set after grouping.

fun main(args: Array<String>) { val test = listOf("a", "ab", "b", Println (" ${test.groupby (String::first)}") println(" ${test.groupby (String::first)}") This condition supports only Boolean conditions. If first satisfies the condition, ForEach {print("$it, ")} println() test.partition {it.length > 1}.first.foreach {print("$it, ")} println() test.partition {it.length > 1 }.second.foreach {print("$it, ")} println();}.second.foreach {print("$it, ")} println(); Println (test.flatmap {it.tolist ()})} println(test.flatmap {it.tolist ()})Copy the code

sortedBy

< < sortedBy > < < sortedBy > < < sortedBy >

fun main(args: Array<String>) {
    val test = listOf(3, 2, 4, 6, 7, 1)
    println(test.sortedBy { it })
}Copy the code

take & slice

Take () and slice() are used to slice data, returning a new set of conditions from a set. Similar examples include takeLast() and takeIf().

fun main(args: Array<String>) { val test = listOf(3, 2, 4, 6, 7, Println (test.slice(IntRange(2, 4)))} println(test.slice(2, 4))}Copy the code

reduce

Fun main(args: Array<String>) {val test = listOf("a", "ab", "b", "BC "); println(test.reduce { acc, name -> "$acc$name" }) }Copy the code

Scoped function

The scoped function mentioned in the previous article is an important example of a higher-order function.

Other properties of lambda expressions

Lazy sequence operation

When some collection functions are chaining, the result of each call is saved as a new temporary list. Therefore, a large number of chaining operations can create a large number of intermediate variables, which can cause performance problems. To improve efficiency, the chaining operation can be changed to sequance.

Call the extension function asSequence to convert any collection into a sequence, and call toList to do the reverse

fun main(args: Array<String>) { val testList = listOf(Test("xys", 18), Test("qwe", 12), Test("rty", 10), Test("zxc", ${testlist.filter {it.age > 10}.map {it.name}}"); ${testlist.filter {it.age > 10} Println (" name ${testlist.assequence ().filter {it.age > 10}.map {it.name}.tolist ()}")} data class  Test(val name: String, val age: Int)Copy the code

A complete sequence consists of two operations, the intermediate sequence and the terminal sequence. The intermediate sequence is always lazy and the terminal sequence triggers all lazy computations.

Testlist.assequence ().filter {.. }.map {.. }.toList()Copy the code

Therefore, by sequencing in this way, you avoid creating large intermediate collections, thus improving performance.

Delay calculation

fun main(args: Array<String>) {
    val addResult = lateAdd(2, 4)
    print(addResult())
}

fun lateAdd(a: Int, b: Int): Function0<Int> {
    fun add(): Int {
        return a + b
    }
    return ::add
}Copy the code

A local function is defined inside lateAdd, and a reference to the local function is returned. The () operator is used to get the final result, delaying the computation.

fun main(args: Array<String>) { val funs = mapOf("sum" to ::sum) val mapFun = funs["sum"] if (mapFun ! = null) { val result = mapFun(1, 2) println("sum result -> $result") } } fun sum(a: Int, b: Int): Int { return a + b }Copy the code

Mastering lambda expressions is like a soldier with a gun. Only by practicing and thinking more can he master the essence of lambda expressions and lay a solid foundation for mastering functional programming in the future.

Welcome to follow my wechat official account — Android Yingchuan