Kotlin understand and use higher-order functions

1. Basic definitions

1.1 What is a higher-order function

By definition, a higher order function is to further function as an argument or function that returns a value .
In Kotlin, the function may be represented by a function or a lambda reference.
Thus, any order lambdaor as a function reference arguments or return value lambdafunction function, or both are satisfied or function references are higher-order functions.

1.2 lambda convention:

Kotlin be familiar with the function, you must first read the code lambda expression, we must first know where a number of conventions, such as:
when the function is only a function as a parameter, and using the lambdaexpression as the corresponding parameters of the function may be omitted parentheses ().
The last parameter of the function is a function of the type, you can use the function parameter lambda expression written on the outside of the parameter list in parentheses.

例如:
str.sumBy( { it.toInt } )
可以省略成
str.sumBy{ it.toInt }

Anko的Context扩展alert函数,可以注意到positiveButton方法第一个参数是text,
第二个参数是监听器lambda表达式,写在了参数列表圆括号外面。
alert("确定删除吗?","Alert") {
    positiveButton("OK") { Log.i(TAG, "你点了确定按钮")}
    negativeButton("Cancel") { Log.i(TAG, "你点了取消按钮") }
}.build().show()

1.3 function type variable corresponding to Java code

In Kotlin, the type of a variable may be 函数类型, for example, the following code sumtypes are variable Int类型, the predicatevariable is 函数类型, that this variable represents a function .

声明一个名字为sum的Int类型变量(这个sum变量的类型是Int)
var sum:Int

声明一个名字为predicate的函数类型变量(这个predicate变量的类型是函数)
predicate是一个以Char为参数,返回值为Boolean的函数。
var predicate: (Char) -> Boolean

声明一个以predicate函数为参数的函数(高阶函数),这个函数的返回类型是String
fun filter(predicate: (Char) -> Boolean) :String

让上面这个函数带上接受者,其实就是给String声明了一个扩展函数。
带上了接收者的函数,函数内部可以直接访问String的其他方法属性,相当于函数内部的this就是String
fun String.filter(predicate: (char) -> Boolean) :String

Kotlin and mixed Java code is invoked, so Kotlin Java function that there is a reference in the corresponding form, that is Functiona reference, Function1<P, R>means that only one parameter type is a reference type of the return value P of R.

2. The standard higher-order functions

2.1 Declaration of higher-order functions

Standard higher-order functions declared in the Standard.ktdocument, including TODO, run, with, apply, also, let, takeIf, takeUnless, repeatfunctions.
We will function in a similar functionality contrast, such as run & with, apply & also, takeIf & takeUnless, let & 扩展函数版本run.

2.2 run & with function

/**
 * Calls the specified function [block] and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

version of the run function has two versions, one is the ordinary version of the specification, one is the extended version of the function
can be seen from the code definition, run函数takes a function reference as an argument (higher-order functions) , within just call this a bit of code return value blocks and block code block.
It can be found withand runare returned block(a function reference) return value.
The difference between what:
The difference is that there is a run extension functions, if needed sentenced to empty prior to use, then 扩展函数版本的run函数use than with函数elegant, such as:

// Yack!
with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
}
// Nice.
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

It can be seen 扩展函数版本的run函数before the call can first determine whether webview.settings is empty, or do not enter the body of the function call.

2.3 apply&also

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

apply& alsoFinally will return to the receiver itself T, it is possible to achieve chained calls to refine the code size, make the code more clear, the difference between them is applythe blockis used T(也就是apply的接收者本身)as the receiver, so applythe blockinside can be accessed T这个this, alsoit is Tbeing passed as a parameter into block, so in alsothe blockinternal need it(lambda的唯一参数)behalf of the alsorecipient T.

Use on:
In general, the lambdaparameters for the itfunction are more suitable for use as reading many occasions, you can use named parameters will be itchanged to the appropriate name to enhance the readability of the code, the Tincoming block(T)occasions more suitable for writing values, because a lot of repeat statements provinces T variables.
[Recommended] block lambda expressions, if mainly writes of an instance, the instance declaredReceiver ; if the main is read, the instance declared parameters .
So long the apply code block write operation value, using also long blocks of code reading operation.

inline fun <T> T.apply(block: T.() -> Unit): T//对T进行写操作,优先使用apply

tvName.apply {
    text = "Jacky"
    textSize = 20f
}

inline fun <T> T.also(block: (T) -> Unit): T //对T进行读操作 优先使用also

user.also {
    tvName.text = it.name
    tvAge.text = it.age
}

2.4 let function

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)

letThis function is 扩展版本的run函数very much like, so I put them in contrast with the above code, the difference between them runis block参数that one 带run接收者T的函数引用, and letthe block参数是to let的接收者Tas a parameter to block, so their difference is the use of calling run, the blockinternal thispointing T, and in letthe blockinternal need itto point to T, letthe blockinternal thisrefers to the Toutside this, which means you are like Activityinside use let, letthe blockinside thisis this Activity实例.

runFunction compares the value suitable for the write more code blocks, letthe function is suitable for multi-reading a code block.

2.4.1 let the return value of the difference also

letReturns block的返回值, alsoreturns 接收者T自身, so they call chain are essentially different.
letYou can achieve similar RxJavain mapeffect

val original = "abc"
// Evolve the value and send to the next chain
original.let {
    println("The original String is $it") // "abc"
    it.reversed() // evolve it as parameter to send to next let
}.let {
    println("The reverse String is $it") // "cba"
    it.length  // can be evolve to other type
}.let {
    println("The length of the String is $it") // 3
}
// Wrong
// Same value is sent in the chain (printed answer is wrong)
original.also {
    println("The original String is $it") // "abc"
    it.reversed() // even if we evolve it, it is useless
}.also {
    println("The reverse String is ${it}") // "abc"
    it.length  // even if we evolve it, it is useless
}.also {
    println("The length of the String is ${it}") // "abc"
}
// Corrected for also (i.e. manipulate as original string
// Same value is sent in the chain 
original.also {
    println("The original String is $it") // "abc"
}.also {
    println("The reverse String is ${it.reversed()}") // "cba"
}.also {
    println("The length of the String is ${it.length}") // 3
}

In the above it seems T.alsolike meaningless, because we can easily combine them into one functional block. But think about it, it has some advantages:

It may be provided on the same object in a very clear separation process , i.e. the production of smaller functional section .
Prior to use, it can achieve a very strong self-manipulation, to achieve chain operation builder (builder mode) .

2.5 takeIf & takeUnless

/**
 * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

/**
 * Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

These two functions are used when used to make a conditional judgment, takeIfit is in predicatecondition to return trueto return when the receiver itself, whether those returns null, takeUnlessis exactly the opposite is predicateto falsereturn the receiver itself, otherwise return null.

2.6 repeat function

/**
 * Executes the given function [action] specified number of [times].
 *
 * A zero-based index of current iteration is passed as a parameter to [action].
 *
 * @sample samples.misc.ControlFlow.repeat
 */
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}

This function is to actionblock repeatedly performed timestwice, actionthe parameter is the currently executed index(many times).

2.7 This vs. it parameters

If you check T.runthe function signature, you will notice that T.runjust as the extended function call block: T.(). Thus, all the range, Tmay be referred to this. In programming, thismost of the time may be omitted. Thus, in our example above, we can printlnuse a statement $lengthinstead ${this.length}. I call transfer thisparameters.
However, for T.letthe function signature, you will notice that T.letpass into his own as a parameter, that is block: (T). So, it's like passing a lambdaparameter. It can be used within the scope of the range itas a reference. So I call transfer itparameters.
Viewed from above, it appears to T.runbe superior, because T.letmore hidden, but this is T.leta function of some subtle advantages are as follows:

T.letCompared external class functions / members, given variable functions / members provides a clearer distinction
in thisthe case can not be omitted, for example when it is passed as a parameter of the function itthan the thisshorter, more clearly.
In T.letvariable allows the use of a better name, you can switch itto a different name.

stringVariable?.let {
      nonNullString ->
      println("The non null string is $nonNullString")
}

2.8 selection of these functions are:

Keep the original type of call chain (T -> T) Call chain into other types (T -> R) Call chain initiation (consider) Call chain applications conditional statements
Write operation T.apply { ... } T.run{ ... } with(T) { ... } T.takeIf / T.takeUnless
Multi-read T.also { ... } T.let{ ... }
436713-5c6f5a2b83364498.png
According to their need to select the appropriate standard higher-order functions

3. Customize the higher-order functions

Code 3.1 debug environment before running

//声明:
inline fun debug(code: () -> Unit){
    if (BuildConfig.DEBUG) {
        code() 
    }
}

//用法:
fun onCreate(savedInstanceState: Bundle?) {
    debug {
        showDebugTools();
    }
}

Function declared as inlineinline code at compile time will be copied and pasted into the corresponding local call, if large and complex function body, does not recommend the use of inline, otherwise it would pack volume increases.

4. Anko related presentations

4.1 Anko Gallery displays a standard dialog box

alert("确定删除吗?","Alert") {
    positiveButton("OK") { Log.i(TAG, "你点了确定按钮")}
    negativeButton("Cancel") { Log.i(TAG, "你点了取消按钮") }
}.build().show()

4.2 Anko library contains several parts.

Anko consists of several parts:

  • Anko Commons: a lightweight library full of helpers for intents, dialogs, logging and so on;
  • Anko Layouts: a fast and type-safe way to write dynamic Android layouts;
  • Anko SQLite: a query DSL and parser collection for Android SQLite;
  • Anko Coroutines: utilities based on the [kotlinx.coroutines]

Public part: intents, dialogs, logging and other higher-order functions.
Layout section: dynamic extension functions quickly add layout, such as the 4.1 standard dialog box that appears.
SQLite parts: a set of query parsing DSL
coroutine part: coroutine related tools.

4.2.1 Anko Layouts (wiki)

Anko Layouts is a DSL for writing dynamic Android layouts. Here is a simple UI written with Anko DSL:

verticalLayout {
    val name = editText()
    button("Say Hello") {
        onClick { toast("Hello, ${name.text}!") }
    }
}

4.2.2 Anko SQLite (wiki)

Have you ever been tired of parsing SQLite query results using Android cursors? Anko SQLite provides lots of helpers to simplify working with SQLite databases.

For example, here is how you can fetch the list of users with a particular name:

fun getUsers(db: ManagedSQLiteOpenHelper): List<User> = db.use {
    db.select("Users")
            .whereSimple("family_name = ?", "John")
            .doExec()
            .parseList(UserParser)
}

Detailed look at Anko Home

5. Resources

Mastering Kotlin standard functions: run, with, let, also and apply
掌握Kotlin标准函数:run, with, let, also and apply
Anko: https://github.com/Kotlin/anko

Reproduced in: https: //www.jianshu.com/p/92c7581ab042

Guess you like

Origin blog.csdn.net/weixin_33670713/article/details/91087294