Android Development Basics - Kotlin: Higher-order Functions

higher order functions

A function is called a higher-order function if it takes another function as an argument, or returns another function of type.

At the same time, the concept of function type is also added in Kotlin, so if the function type is added to the parameter declaration or return value declaration of a function, the function is a higher-order function.

The syntax rules for function types are:

(String, Int) -> Unit

In the above definition, the part on the left of -> is used to declare what parameters the function receives, and multiple parameters are separated by commas. If no parameters are received, just use empty brackets, and the part on the right of -> is used to declare What type is the return value of this function, if there is no return value, it is Unit.

fun num1AndNum2(num1:Int, num2:Int, operator:(Int, Int) -> Int):Int {
    val result = operator(num1, num2)
    return result
}

fun plus(num1: Int, num2: Int):Int {
    return num1 + num2
}

fun minus(num1: Int, num2: Int):Int {
    return num1 - num2
}

fun main() {
    println(num1AndNum2(1,2, ::plus))
    println(num1AndNum2(1,2, ::minus))
}

In the above code, the plus and minus methods are defined at the same time, and passed as parameters to the num1AndNum2 method, which means that the num1AndNum2 method can ignore the name of the incoming function type parameter, and only pay attention to the parameters of the function type parameter, as long as they are the same parameters Types can be passed in as parameters, and the same interface can be used for different operations in the function body.

It should be noted that the form of ::plus needs to be used when calling to indicate that this is a function type parameter, which is the way of writing function references.

Of course, higher-order functions can also be called in the form of Lambda, anonymous functions, and member references.

fun main() {
    println(num1AndNum2(1,2) {n1, n2 -> n1 + n2})
    println(num1AndNum2(1,2) {n1, n2 -> n1 - n2})
}

The above code is in the form of Lambda, which is the same as the previous execution result.

Let's look at another example:

fun StringBuilder.build(block:StringBuilder.() -> Unit):StringBuilder {
    block()
    return this
}

fun main() {
    val list = listOf("Apple","Banana","Orange","Pear","Grape")
    val result = StringBuilder().build {
        append("Start eating fruits.\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
    }
    println(result.toString())

    val result2 = StringBuilder().apply {
        append("Start eating fruits.\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
    }
    println(result2.toString())
}

The above two ways of writing are equivalent. In the way of writing high-order functions, the build extension function is first defined for StringBuilder. This extension function receives a function type parameter and the return value type is StringBuilder.

Then the parameter type parameter adopts the form of StringBuilder.(), indicating that the function is a function in StringBuilder. The Lambda expression passed in in the actual call will automatically have the context of StringBuilder, which is consistent with the execution result of apply.

inline function

Inline functions only need to add the inline keyword when defining higher-order functions, such as:

inline fun num1AndNum2(num1:Int, num2:Int, operator:(Int, Int) -> Int):Int {
    val result = operator(num1, num2)
    return result
}

An inline function is an automatic replacement of the code when the code is compiled. This method can eliminate the runtime overhead of using Lambda expressions as parameters of higher-order functions.

The inline function definition here is similar to the inline function definition in C++. In short, it can eliminate the runtime overhead caused by Lambda expressions.

noinline/crossinline

And if a higher-order function accepts two or more parameters of function type, add the inline keyword to the function at this time, and the Kotlin compiler will automatically inline all referenced Lambda expressions.

And if you only need to inline one of the Lambda expressions, you can use the noinline keyword:

inline fun inlineTest(block1:() -> Unit, noinline block2: () -> Unit):Int {
    //TODO
}

The above code will only inline block1 because of the effect of noinline. The reason why inlining is canceled is because inline function type parameters will be replaced by code at compile time, so they have no real parameter attributes, while non-inline function type parameters can be freely passed to any function, because their is a real parameter, and an inline function type parameter is only allowed to be passed to another inline function.

At the same time, it should be noted that the return keyword can be used in the Lambda expression referenced by the inline function to return the function, while the non-inline function can only return locally.

fun printString(str:String, block:(String) -> Unit) {
    println("printString start")
    block(str)
    println("printString end")
}

fun main() {
    println("main start")
    val str=""
    printString(str) { s ->
        println("lambda start")
        if (s.isEmpty()) return@printString
        println(s)
        println("lambda end")
    }
    println("main end")
}

The above code defines the high-order function of printString, which is used to print the incoming string parameter in the Lambda expression. If the parameter is empty, it will not be printed. At the same time, the return keyword is not allowed to be used directly in the Lambda expression, so the writing method of return@printString is used to indicate a partial return, and the rest of the code of the Lambda expression will not be executed.

Here an empty string parameter is passed in, and the result is:

main start
printString start
lambda start
printString end
main end

Judging from the above results, return@printString can only return partially.

Here the printString function is declared as an inline function:

inline fun printString(str:String, block:(String) -> Unit) {
    println("printString start")
    block(str)
    println("printString end")
}

fun main() {
    println("main start")
    val str=""
    printString(str) { s ->
        println("lambda start")
        if (s.isEmpty()) return
        println(s)
        println("lambda end")
    }
    println("main end")
}

The result is:

main start
printString start
lambda start

Now that the printString function is declared as an inline function, the return keyword can be used in the Lambda expression. At this time, return means returning the calling function of the outer layer, that is, the main function.

inline fun runRunnable(block:() -> Unit) {
    val runnable = Runnable {
        block()
    }
    
    runnable.run()
}

Although you can use inline to declare higher-order functions to eliminate the runtime overhead of Lambda. But the above code will report an error, because in the runRunnable function, the Runnable object is created, and the function type parameter passed in is called in the Lambda expression of Runnable. The lambda expression will be converted to the implementation method of the anonymous class when compiling, that is to say, the above code actually calls the incoming function type parameter in the anonymous class.

The Lambda expression referenced by the inline function allows the use of the return keyword to return the function, while the function type parameter called in the anonymous class cannot return the function call of the outer layer, and can only return the function call in the anonymous class at most. , so an error will be reported.

That is to say, if you create another Lambda or anonymous class implementation in the higher-order function, and call function type parameters in these implementations, and declare the higher-order function as an inline function, an error will be reported.

This problem can be solved with the help of the crossiinline keyword:

inline fun runRunnable(crossinline block:() -> Unit) {
    val runnable = Runnable {
        block()
    }
    
    runnable.run()
}

 The previous error was due to a conflict between the return keyword allowed in the lambda expression in the inline function and the return keyword not allowed in the anonymous class implementation of the higher-order function. The crossinline keyword ensures that the return keyword will not be used in the Lambda expression of the inline function, so that conflicts do not exist.

After the crossinline is declared, the return keyword cannot be used in the lambda expression for function return, but reutrn@func can still be used for partial return.

In general, in addition to the above differences, most high-order functions can be directly declared as inline functions, which is also a good programming practice.

Guess you like

Origin blog.csdn.net/SAKURASANN/article/details/127041950