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 lambda
or as a function reference arguments or return value lambda
function 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 lambda
expression 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 sum
types are variable Int类型
, the predicate
variable 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 Function
a 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.kt
document, including TODO
, run
, with
, apply
, also
, let
, takeIf
, takeUnless
, repeat
functions.
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 with
and run
are 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
& also
Finally 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 apply
the block
is used T(也就是apply的接收者本身)
as the receiver, so apply
the block
inside can be accessed T这个this
, also
it is T
being passed as a parameter into block
, so in also
the block
internal need it(lambda的唯一参数)
behalf of the also
recipient T
.
Use on:
In general, the lambda
parameters for the it
function are more suitable for use as reading many occasions, you can use named parameters will be it
changed to the appropriate name to enhance the readability of the code, the T
incoming 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)
let
This function is 扩展版本的run函数
very much like, so I put them in contrast with the above code, the difference between them run
is block参数
that one 带run接收者T的函数引用
, and let
the block参数是
to let的接收者T
as a parameter to block
, so their difference is the use of calling run
, the block
internal this
pointing T
, and in let
the block
internal need it
to point to T
, let
the block
internal this
refers to the T
outside this
, which means you are like Activity
inside use let
, let
the block
inside this
is this Activity实例
.
run
Function compares the value suitable for the write more code blocks, let
the function is suitable for multi-reading a code block.
2.4.1 let the return value of the difference also
let
Returns block的返回值
, also
returns 接收者T自身
, so they call chain are essentially different. let
You can achieve similar RxJava
in map
effect
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.also
like 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, takeIf
it is in predicate
condition to return true
to return when the receiver itself, whether those returns null
, takeUnless
is exactly the opposite is predicate
to false
return 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 action
block repeatedly performed times
twice, action
the parameter is the currently executed index
(many times).
2.7 This vs. it parameters
If you check T.run
the function signature, you will notice that T.run
just as the extended function call block: T.()
. Thus, all the range, T
may be referred to this
. In programming, this
most of the time may be omitted. Thus, in our example above, we can println
use a statement $length
instead ${this.length}
. I call transfer this
parameters.
However, for T.let
the function signature, you will notice that T.let
pass into his own as a parameter, that is block: (T)
. So, it's like passing a lambda
parameter. It can be used within the scope of the range it
as a reference. So I call transfer it
parameters.
Viewed from above, it appears to T.run
be superior, because T.let
more hidden, but this is T.let
a function of some subtle advantages are as follows:
T.let
Compared external class functions / members, given variable functions / members provides a clearer distinction
in this
the case can not be omitted, for example when it is passed as a parameter of the function it
than the this
shorter, more clearly.
In T.let
variable allows the use of a better name, you can switch it
to 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{ ... } |
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 inline
inline 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