Define higher-order functions

The relationship between higher-order functions and Lamdba is inextricable. Functions that take Lambda arguments can be called functional programming style apis, whereas if you want to define your own functional API, you have to use higher-order functions to do so.

High-order function definition: A function is called high-order if it takes another function as an argument, or if the type of the return value is another function. (The other function here refers to function types, like integers, etc.)

Basic syntax rules for function types :(String, Int) -> Unit

-> The left part is used to declare what arguments the function accepts. Multiple arguments are separated by commas. If no arguments are accepted, empty parentheses are used. The -> part on the right declares the return value type of the function. If there is no return value, Unit is used, which is roughly equivalent to void in Java.

High-order function definition: a function is a higher-order function by adding its type to its argument or return value declaration.

fun example (func:  (String.Int) - >Unit){
   func("hello".123)}Copy the code

Higher-order functions are useful: Higher-order functions allow the arguments of a function type to determine the execution logic of the function.

fun main(a){
    val num1 = 100
    val num2 = 80
    // ::plus this is a function reference way of saying that plus() is passed as an argument to num1AndNum2().
    val result1 = num1AndNum2(num1,num2,::plus)
    val result2 = num1AndNum2(num1,num2,::minus)
    println("result1 is $result1")
    println("result2 is $result2")}The third argument is a function type argument that takes two integer arguments and returns an integer as well. * /
fun num1AndNum2(num1:Int, num2:Int, operation:(Int.Int) - >Int):Int{
    val result = operation(num1,num2)
    return result
}
fun plus(num1: Int,num2: Int):Int{
    return num1 + num2
}
fun minus(num1: Int,num2: Int):Int{
    return num1 - num2
}
Copy the code

Kotlin also supports a variety of other ways to call higher-order functions, such as Lambda expressions, anonymous functions, member references, and so on.

fun main(a){
    val num1 = 100
    val num2 = 80
    // Use Lambda expressions
    Lambda expressions can also fully express a function's argument and return value declarations, and are written in a more concise way.
    // (The last line in the Lambda expression is automatically returned as the value)
    val result1 = num1AndNum2(num1,num2){ n1, n2 ->
        n1 + n2
    }
    val result2 = num1AndNum2(num1,num2){ n1, n2 ->
        n1 - n2
    }
    println("result1 is $result1")
    println("result2 is $result2")}Copy the code
fun main(a){
    val list = listOf("Apple"."Banana"."Pear")
    val result = StringBuilder().build {
        append("Start eating fruits.\n")
        for (fruit in list){
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
    }
    println(result.toString())
}

/** * implements a function similar to apply(), except that it applies only to the StringBuilder class and requires generics to apply() to all classes. * Defines a build extension function for the StringBuilder class that takes a function type argument and returns a Value type of StringBuilder. The syntax of a StringBuilder is a complete set of rules for defining higher-order functions. The syntax of a StringBuilder is a complete set of rules for defining higher-order functions. The syntax of a StringBuilder is a complete set of rules for defining higher-order functions. * The Lambda expression passed in when build() is called will automatically have the StringBuilder context, and this is how apply is implemented. * /
fun StringBuilder.build(block: StringBuilder. () - >Unit): StringBuilder{
    block()
    return this
}
Copy the code

The role of inline functions

The implementation principle of higher-order functions:

Kotlin’s code will eventually be compiled into Java bytecode, but there is no concept of higher-order functions in Java.

Kotlin’s compiler translates the syntax of these higher-order functions into Java supported syntactic constructs, while Lambda expressions are translated into anonymous class implementations underneath. Each time a Lambda expression is called, a new anonymous class instance is created, which of course incurs additional memory and performance overhead.

Inline functions, on the other hand, can completely eliminate the runtime overhead of using Lambda expressions.

/** * Inline function usage: * only need to define higher-order functions with the inline keyword declaration **/
inline fun num1AndNum2(num1:Int, num2:Int, operation:(Int.Int) - >Int):Int{
    val result = operation(num1,num2)
    return result
}
Copy the code

How inline functions work:

/** * The Kotlin compiler automatically replaces the code in the inline function at compile time to where it was called, so there is no runtime overhead. * /
fun main(a){
    val num1 = 100
    val num2 = 80
    val result = num1AndNum2(num1,num2){n1,n2 ->
        n1 + n2
    }
    println(result)
}

fun num1AndNum2(num1:Int, num2:Int, operation:(Int.Int) - >Int):Int{
    val result = operation(num1,num2)
    return result
}
Copy the code
/** * How it works * First, the Kotlin compiler replaces the code in the Lambda expression where the function type argument is called. * /
fun main(a){
    val num1 = 100
    val num2 = 80
    val result = num1AndNum2(num1,num2)
    println(result)
}

fun num1AndNum2(num1:Int, num2:Int):Int{
    val result = num1 + num2
    return result
}


/** * then replace all the code in the inline function at the place of the function call. * Because of this, inline functions can completely eliminate the runtime overhead of Lambda expressions. * /
fun main(a){
    val num1 = 100
    val num2 = 80
    val result = num1 + num2
    println(result)
}
Copy the code

Noinline and crossinline

If a higher-order function receives two or more arguments of function type inline, the Kotlin compiler automatically inlines all referenced Lambda expressions.

But if you want to inline only one of the Lambda expressions, you can use the noinline keyword.

/** * only Lambda expressions referenced by block1 arguments are inlined */
inline fun inlineTest(block1:() -> Unit.noinline block2:() -> Unit){}
Copy the code

The meaning of the noinline keyword is that an inline function type parameter is replaced by code at compile time, so it has no real parameter attribute. A non-inline function type parameter can be passed freely to any other function because it is a real parameter, whereas an inline function type parameter can only be passed to one other inline function, which is its biggest limitation.

There is another important difference between inline and non-inline functions: the return keyword can be used to return a function from a Lambda expression referenced by an inline function, whereas non-inline functions can only return locally.

/** * non-inline function */
fun main(a){
    println("main start")
    val str = ""
    printString(str){ s ->
        println("lambda start")
        // Non-inline functions can only be written as return@printString to indicate partial return,
        // And no longer executes the rest of the Lambda expression code.
        if (s.isEmpty()) return@printString
        println(s)
        println("lambda end")
    }
    println("main end")

    // Print the result:
    // main start
    // printString begin
    // lambda start
    // printString end
    // main end
    // Except for the code following the return@printString statement in the Lambda expression, the logs are printed normally, indicating that return@printString does return only partially.
}

fun printString(str:String,block:(String) - >Unit){
    println("printString begin")
    block(str)
    println("printString end")}Copy the code
/** * inline function */
fun main(a){
    println("main start")
    val str = ""
    printString(str){ s ->
        println("lambda start")
        // In addition to return@printString, inline functions can use the return keyword,
        // Return refers to the outer calling function (main)
        if (s.isEmpty()) return
        println(s)
        println("lambda end")
    }
    println("main end")

    // Print the result:
    // main start
    // printString begin
    // lambda start
    // Either main() or printString() stops executing after the return keyword.
}

inline fun printString(str:String,block:(String) - >Unit){
    println("printString begin")
    block(str)
    println("printString end")}Copy the code

It is good programming practice to declare higher-order functions as inline functions. In fact, most higher-order functions can be declared as inline functions, but there are a few exceptions:

/** * inline, block() will report an error. Lambda expressions referenced by inline functions allow function returns using the return keyword, * but because the function type parameter is called in an anonymous class, it is not possible to return the outer call function. * Returns only function calls in an anonymous class, so there is an error. * * That is, if another implementation of a Lambda or anonymous class is created in a higher-order function and the function type argument is called in those implementations, an error will be generated if the higher-order function is declared as an inline function. The crossinline keyword solves this problem. * /
inline fun runRunnable(block:() -> Unit){
    // Creates a Runnable object in the function and calls the function type argument passed in the expression
    Lambda expressions are converted to anonymous classes at compile time.
    // That is, the function type argument passed in is actually called in the anonymous class.
    val runnable = Runnable{
        block()
    }
    runnable.run()
}

/** * The above error was caused by a conflict between the allowed return keyword in Lambda expressions for inline functions and the disallowed return keyword in anonymous class implementations of higher-order functions. The crossinline keyword is useful for this: * It is like a contract that guarantees that the return keyword will never be used in inline function Lambda expressions, * but return can still be used@printStringIs written to perform a local return. * * In general, Crossinline retains all the other features of inline functions except the use of the return keyword. * /
inline fun runRunnable(crossinline block:() -> Unit){
    val runnable = Runnable{
        block()
    }
    runnable.run()
}
Copy the code

Application of higher order functions

Higher-order functions are very useful for simplifying calls to various apis, and some of the original uses of apis can be greatly improved in terms of ease of use and readability.

Simplify the use of SharedPreferences

/** * add an open function to SP by extending the function, and it takes an argument of function type, so open is naturally a higher-order function. * * Since the SP context is in the open function, you can call edit() directly to get the sharedPreferences.edit () object. * * The open function receives a sharedPreferences.editor function type parameter, so editor.block() is called to add data to the concrete implementation of the function type parameter. Finally, the data store operation is completed by calling editor.apply() to commit the data. * /
fun SharedPreferences.open(block: SharedPreferences.Editor. () - >Unit){
    val editor = edit()
    editor.block()
    editor.apply()
}
Copy the code

Usage:

        // Store data
        btnSave.setOnClickListener{
// val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
// editor.putString("name","Tom")
// editor.putInt("age",28)
// editor.putBoolean("married",false)
// editor.apply()

            // Use higher-order functions to simplify the use of SP.
// getSharedPreferences("data",Context.MODE_PRIVATE).open {
// // has the context of SharedPreferences.Editor, so you can add data by calling the corresponding PUT method directly here.
// putString("name","Tom")
// putInt("age",28)
// putBoolean("married",false)
/ /}

            // In fact, this simplification is already included in the Google KTX extension library:
            / / implementation 'androidx. Core: the core - KTX: 1.3.0'
            // However, understanding the principles described above will help you extend more apis in the future.
            getSharedPreferences("data",Context.MODE_PRIVATE).edit {
                putString("name"."Tom")
                putInt("age".28)
                putBoolean("married".false)}}Copy the code

Simplify the use of ContentValues

/** * The return value of the apply function is the object itself, so we can use the syntactic sugar of single-line functions to replace the declaration of the return value with an equal sign. * In addition, the apply function automatically has a ContentValues context in its Lambda expression, so you can call the PUT method directly. * /
fun cvOf(vararg pairs: Pair<String, Any? >) = ContentValues().apply{
    for (pair in pairs){
        val key = pair.first
        val value = pair.second
        when(value){
            // The Smart Cast function in Kotlin is also used here.
            // For example, when the statement enters the Int condition branch, the value under the condition is automatically converted to Int instead of Any? Type,
            // This eliminates the need for an additional downward transition as in Java, which also applies to if statements.
            is Int -> put(key, value)
            is Long -> put(key, value)
            is Short -> put(key, value)
            is Float -> put(key, value)
            is Double -> put(key, value)
            is Boolean -> put(key, value)
            is String -> put(key, value)
            is Byte -> put(key, value)
            is ByteArray -> put(key, value)
            null -> putNull(key)
        }
    }
}

/** * This method builds a ContentValues object. The * * mapOf() function allows you to quickly create a key-value pair using syntax like "Apple" to 1. * Using A syntax like A to B in Kotlin creates A Pair object. * * The method takes a Pair argument. The vararg keyword corresponds to the Java variable argument list. * Allows you to pass zero, one, or any number of Pair arguments to this method. A for-in loop is then used to iterate over all the arguments passed in. * * Pair is a key-value data structure, so you need generics to specify what type of data its keys and values correspond to. * ContentValues are strings, so you can specify the Pair key as String. * But ContentValues can be of many types (String, integer, float, or even NULL), so specify Any. * Any is the common base class for all classes in Kotlin, equivalent to Java's Object, while Any? Indicates that null values are allowed to be passed in. * /
//fun cvOf(vararg pairs: Pair
      
       ): ContentValues{
      ,>
// // creates the ContentValues object
// val cv = ContentValues()
// // iterates through the list of pairs parameters, retrieves the data and fills it into ContentValues, finally returning the ContentValues object.
// for (pair in pairs){
// val key = pair.first
// val value = pair.second
// // uses the WHEN statement to judge one by one and covers all data types supported by ContentValues.
// // (because the Pair parameter is Any? Type)
// when(value){
// // Also uses the Smart Cast feature in Kotlin.
// // For example, if the when statement branches into an Int condition, the value under the condition is automatically converted to an Int instead of an Any? Type,
// // eliminates the need for an additional downward transition like in Java, which also works in if statements.
// is Int -> cv.put(key, value)
// is Long -> cv.put(key, value)
// is Short -> cv.put(key, value)
// is Float -> cv.put(key, value)
// is Double -> cv.put(key, value)
// is Boolean -> cv.put(key, value)
// is String -> cv.put(key, value)
// is Byte -> cv.put(key, value)
// is ByteArray -> cv.put(key, value)
// null -> cv.putNull(key)
/ /}
/ /}
// return cv
/ /}
Copy the code

Usage:

// val values = ContentValues()
// values.put("name","Game of Thrones")
// values.put("author","George Martin")
// values.put("pages",720)
/ / values. The put (" price ", 20.85)

                // use the apply function to simplify writing
// val values = ContentValues().apply {
// put("name","Game of Thrones")
// put("author","George Martin")
// put("pages",720)
/ / put (" price ", 20.85)
/ /}

                // Use higher-order functions to simplify usage
// val values = cvOf("name" to "Game of Thrones","author" to "George Martin",
// "pages" to 720,"price" to 20.85)

                // In fact, the KTX library provides a method with the same functionality
                val values = contentValuesOf("name" to "Game of Thrones"."author" to "George Martin"."pages" to 720."price" to 20.85)

                db.insert("Book".null,values)
Copy the code

note

References:

Line 1 of code (Version 3)

The official documentation

Official Chinese translation site

Welcome to follow wechat official account:No reason also