Higher-order functions
What is?
A function that takes another function as an argument, a return value, or both is called a higher-order function
Function types
Integer types can hold integers, string types can hold strings, and function types can hold function references
A function itself cannot be counted as an object or passed as an argument, but we can generalize the argument list and return value of that function into a type (function type) and use the object defined by that type to represent the function (function reference), so that we can use that object as an argument or return value to other functions
val sum: (Int.Int) - >Int = { x, y -> x + y }
val action: () -> Unit = { println(42)}var canResultNull: (Int.Int) - >Int? = { null }
var funOrNull: ((Int.Int) - >Int)? = null
Copy the code
The above (Int, Int) -> Int and () -> Unit are function types
Parameter name of the function type
fun performRequest(url: String, callback: (Int.String) - >Unit) {
// ...
}
fun performRequest(url: String, callback: (code: Int.content: String) - >Unit) {
// ...
}
Copy the code
(1) callback: (Int, String) -> Unit (2) callback: (code: Int, content: String) -> Unit
But the function type parameter name does not need to match, it is just for code readability
Call the function as an argument
fun twoAndThree(operator: (Int.Int) - >Int) {
val result = operator(2.3)
println("The result is ${result}")}Copy the code
Implement a simplified version of filter
private fun String.myFilter02(predicate: Char. () - >Boolean): String {
val sb = StringBuilder()
this.forEach {
if (predicate(it)) {
sb.append(it)
}
}
return sb.toString()
}
private fun String.myFilter01(predicate: (Char) - >Boolean): String {
val sb = StringBuilder()
this.forEach {
if (predicate(it)) {
sb.append(it)
}
}
return sb.toString()
}
fun main(a) {
val str: String = "abc123"
println(str.myFilter01 { it in 'a'.'z' })
println(str.myFilter02 { this in 'a'.'z'})}Copy the code
Function type default argument
private fun String.filter(predicate: (Char) - >Boolean = { it in 'a'.'z' }) {
// ...
}
Copy the code
Function that returns a function
Return different logic depending on certain criteria (function reference + function argument stack)
enum class Delivery {
STANDARD, EXPEDITED
}
class Order(val itemCount: Int)
fun getShippingCostCalculator(delivery: Delivery): (Order) -> Double {
if (delivery == Delivery.EXPEDITED) {
return {order -> 6 + 2.1 * order.itemCount }
}
return { order -> 1.2 * order.itemCount }
}
fun main(a) {
val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
println(calculator(Order(3)))}Copy the code
Return function saves function reference + function stack frame, look at the following code:
fun sum(a: Int, b: Int): Int {
return a + b
}
fun myFun(a: Int, b: Int): () - >Int {
return { sum(a, b) }
}
fun main(a) {
// mFun holds function references and function argument stack frames
val mFun = myFun(20.39)
// So it will print 59 here, because it has stack frame, save a = 20, b = 39
val i = mFun()
println(i) / / 59
}
Copy the code
One use case for lambda: removing duplicate code
We now analyze the case of website access speed of different platforms, using OS enumeration to distinguish different platforms, using data Class SiteVisit data class, store access path, time and platform
enum class OS {
WINDOWS, MAC, ANDROID, LINUX, IOS
}
data class SiteVisit(
val path: String,
val duration: Double.val os: OS
)
fun main(a) {
val log = listOf(
SiteVisit("/".6.9, OS.LINUX),
SiteVisit("/".34.0, OS.WINDOWS),
SiteVisit("/".22.0, OS.MAC),
SiteVisit("/login".12.1, OS.WINDOWS),
SiteVisit("signup".8.0, OS.IOS),
SiteVisit("/".16.3, OS.ANDROID), SiteVisit("/".8.2, OS.LINUX)
)
}
Copy the code
Now we need to know how much time the Window platform spends on average:
println(log.filter { it.os == OS.WINDOWS }.map(SiteVisit::duration).average())
Copy the code
Then the requirements changed and now you need to know the average speed of the Linux platform:
println(log.filter { it.os == OS.LINUX }.map(SiteVisit::duration).average())
Copy the code
What about next time? The code needs to be changed to generalize the variable OS, which is always changing, as a function parameter
fun List<SiteVisit>.averageDurationFor(os: OS) = filter { it.os == os }.map(SiteVisit::duration).average()
Copy the code
>>> println(log.averageDurationFor(OS.ANDROID))
Then the requirements change again, this time to know the average speed of mobile access. The code above has to change again:
fun List<SiteVisit>.averageDurationFor(multiOs: Set<OS>) = filter { it.os in multiOs }.map(SiteVisit::duration).average()
Copy the code
>>> println(log.averageDurationFor(setOf(OS.ANDROID, OS.IOS)))
There is also a more liberal use of this code, including the one I used before, because Java is cheap to write (to use a lambda, Java must define an SAM interface, or use an off-the-shelf SAM interface). Whereas Kotlin’s lambda is convenient, we can use lambda
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) - >Boolean) = filter(predicate).map(SiteVisit::duration).average()
Copy the code
>>> println(log.averageDurationFor { it.os in setOf(OS.ANDROID, OS.IOS) })
Inline functions, eliminating the cost of lambda
What is?
Inline function, the main function is to inline in one place, paste (function body) everywhere
Function and c language macro definition is very similar, mainly in the call to the location of the inline function, directly copy the body of the inline function, and then remove the function name and scope (i.e. {} parentheses).
Also, inline functions can inline incoming lambda arguments that have already been called
Remember that lambda expressions that have already been passed in and called are only inline if they’ve already been called
inline fun f(param: () -> Unit) {
println("inline function")
param()
return
}
fun main(a) {
f { println("lambda")}}Copy the code
After decompiling:
String var1 = "inline function";
System.out.println(var1);
String var4 = "lambda";
System.out.println(var4);
Copy the code
Incoming objects cannot be inlined
Note that only incoming lambda arguments that have already been called can be inlined. If an object is passed in, the object cannot be inlined.
fun main(a) {
val v: () -> Unit = { println("lambda") }
f(v)
}
Copy the code
The decompiled Java source looks like this:
Function0 v = (Function0)null.INSTANCE;
String var2 = "inline function";
System.out.println(var2);
v.invoke();
Copy the code
Do you see that? Function0 v = (Function0)null.INSTANCE; The v is the previous variable, and the lambda expression has become an object
The compiler encounters general processing steps for an inline function
Original code:
inline fun f(param: () -> Unit): Int {
println("inline function")
param()
return Random(100).nextInt()
}
fun main(a) {
println("Before entering f function:")
val v = f { println("lambda") }
println("v = $v")
println("After entering the f function")}Copy the code
- Inline the lambda expression for the function body
inline fun f(param: () -> Unit): Int {
println("inline function")
println("lambda") // Inline lambda expressions
return Random(100).nextInt()
}
Copy the code
- Copy the body of the function to the position where the inline function was called
return
Under the deal with
Remember it’s a return from an inline function, not a lambda return
fun main(a) {
println("Before entering f function:")
println("inline function")
println("lambda") // Inline lambda expressions
val v = Random(100).nextInt()
println("v = $v")
println("After entering the f function")}Copy the code
So, we’ve done the general process of inlining functions, and we’ll talk about what happens when we pass parameters during the inlining process, which we’ll talk about later. Okay
Notice the return? If you look at the bytecode I intentionally gave you, you can see that the println(” after the function executes “) code will not execute, but in fact it will execute, so the inline function code is not copied directly, but is treated in a special way
Why inline functions?
Answer: To make lambda arguments in inline functions no longer create objects, look at the code:
fun f(param: () -> Unit) {
println("inline function")
param()
}
fun main(a) {
println("Before entering f function:")
f { println("lambda") }
println("After entering the f function")}Copy the code
If you decompress, you’ll find that f {println(“lambda”)} becomes f((Function0) null.instance); See??????? The lambda expression creates an object, which is a loss
In Kotlin, there are many, many higher-order functions with functions of type such as f. Passing a lambda to one of these higher-order functions requires the creation of an object, which is too inefficient, so you have to use inline functions
As mentioned earlier, with an inline function, the location of the call to the inline function is replaced by the function body of the inline function, thus creating less {println(“lambda”)} objects
What are the disadvantages?
A: The disadvantages are obvious. The main purpose of the inline function is to prevent the lambda from creating too many objects. However, if the function body of the inline function is large and the position of the function call is large, it will cause a lot of bytecode bloat, which will affect the size of the APK package
Therefore, the inline function is usually short, especially when the project has strict requirements on the size of app, more attention should be paid
Higher-order functions control flow
A non-inline lambda in Kotlin does not allow display using return(it does not need to, as long as the return value is placed at the end of the expression), whereas an inline lambda expression can display using return, but returns the function from which it was called. The return of the inline lambda penetrates one layer of scope
Lambda returns the value of the last row
fun running(a: Int, b: Int, funcType: (Int.Int) - >Int) = funcType(a, b)
fun main(a) {
running(10.20) {x: Int, y: Int ->
x + y
}
}
Copy the code
The above return is the same as the following
fun main (a) {
running(10.20.fun(x: Int, y: Int) {
return x + y
})
}
Copy the code
Inline functionNonlocal return
A return in the inline function type argument lambda of an inline function call penetrates an outer scope
inline fun running(a: Int, b: Int, funcType: (Int) - >Int) {
funcType(a + b)
}
fun main(a) {
println("Before calling:")
// Return it returns main
running(10.20) {
println(it)
return it // This is a nonlocal return
}
println("After call")}Copy the code
The above return it is a non-local return that returns main
The solution is also simple, return@running it is done, so it returns running instead of main, which is called a tag, much like the old method of calling the parent class
noinline
Lambda function type parameter that is not called
The noinline flag must be used when a lambda expression (argument) is passed to a function type argument (parameter) of an inline function if the lambda is not called but is used for return or stored in a variable. Otherwise, function type parameter names will be copied to the past due to the existence of inline, resulting in unrecognizable external functions
inline fun running(funcType01: (String) - >Unit.noinline funcType02: (Int) - >Unit) {
funcType01("zhazha")
return funcType02
}
fun main(a) {
running({ println(it) }, { println(it * 10)})}Copy the code
If noinline was not used, it would look like this:
crossinline
Usage scenarios of
Lambda is used when the function argument lambda is used in the function scope of the function, namely, indirect call
fun interface MyRunnable {
fun myF(a)
}
inline fun running(a: Int, b: Int.crossinline funcType: (Int.Int) - >Int) {
MyRunnable {
// use within the scope of the scope
println(funcType(a, b))
}.myF()
}
fun main(a) {
running(10.20) { x, y ->
x + y
}
}
Copy the code
Crossinline should be inline with a function type argument, which is used to prevent indirect calls when the function type argument is passed the body of a lambda function. If the indirect call has a return in the lambda, this modifier can prevent non-local returns. Causes the function to return the RUNNING function through one layer of scope
Local return in lambda
labeledreturn
What is?
Similar to the break in the for loop
val list = arrayListOf(Person("a".1), Person("b".2), Person("c".3))
for (person in list) {
if (person.name == "b") {
break
}
println(person)
}
list.forEach {
if (it.name == "b") {
return@forEach
}
println(it)
}
list.forEach label@ {
if (it.name == "b") {
return@label
}
println(it)
}
Copy the code
All three of the above methods have similar functions
Tag is tag + @ tag before lambda expression tag @{/* lambda */}
When you use it, it’s return + @ + with no Spaces in it, it’s return
Tagged this
As before, tag lambda with @, and then use this@ with.xxxx
println(StringBuilder().apply sb@ {
listOf(1.2.3).apply {
this@sb.append(this.toString())
}
})
Copy the code
The append IDEA of [email protected] cannot be prompted intelligently, so we need to write it by hand
Anonymous functions use local returns
inline fun running(funcType01: (String) - >Int.noinline funcType02: (Int) - >Unit): (Int) - >Unit {
println(funcType01("zhazha"))
return funcType02
}
fun main(a) {
println("Before calling:")
running(fun(str: String): Int {
println(str)
return str.length
}) {
println(it * 100)
}
println("After call")}Copy the code
Anonymous functions have been studied in previous chapters and will not be explained here