[Kotlin] DSL domain-specific language (apply standard library function analysis | ordinary anonymous function | extended anonymous function | generic extended anonymous function)


Summary of this chapter: Understand the apply standard library function

public inline fun <T> T.apply(block: T.() -> Unit): T

The core is its block: T.() -> Unitparameter, which is a generic extended anonymous function;

Generic extended anonymous function T.() -> Unit evolution path:

  • Ordinary anonymous function: () -> Unit , the parameters and return value of this function are empty;
  • Extended anonymous function: String.() -> Unit , this function is an extension function defined for the specific String type;
  • Generic extended anonymous function: T.() -> Unit , this function is a generic extended anonymous function defined for all types, and all classes can call this anonymous function;




1. DSL Domain Specific Language



In Kotlin, the standard library function apply function is defined , and the function prototype is as follows:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    
    
    block()
    return this
}

The generic extended anonymous function that receives the parameter type T.() -> Unit exposes the function characteristics of the receiver to facilitate the use of Lambda expressions to read and configure the receiver object;

    "123".apply {
    
    
        println(this)
    }

The above writing method is a programming paradigm provided by Kotlin , which exposes the function characteristics of the receiver to facilitate the use of Lambda expressions to read and configure the receiver object; with the help of this programming paradigm, DSL domain-specific languages ​​can be written;





2. Analysis of apply standard library function



Supports implicit calling of receiver objects in the apply function ;


1. Apply function display


As shown below: call the apply extension function of the "123" string . In the closure parameter of the function, this is the receiver "123" string . In the Lambda expression, you can directly call the string method;
therefore, call println(this) code, printing this is printing the "123" string;

Calling length is calling this.length to get the length of the "123" string;

fun main() {
    
    
    "123".apply {
    
    
        println(this)
        
        var strLen = length
        println(strLen)
    }
}

2. Apply function prototype analysis


function prototype

The apply function prototype is as follows: This function is defined in the Standard.kt script and is a generic extension function . All types can use this extension function;

/**
 * Calls the specified function [block] with `this` value 
 * as its receiver and returns `this` value.
 * 以' this '值作为接收者调用指定函数[block],并返回' this '值。
 *
 * For detailed usage information see the documentation for [scope functions]
 * (https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

Parameter and return value analysis

In the apply function, the parameter type received is block: T.() -> Unit , which is a Lambda expression/anonymous function/closure,

The Lambda expression block type is T.() -> Unit , and its return value is Unit type, which means there is no return value;

The final generic extension function defined for generic T is fun T.apply(block: T.() -> Unit): T , which returns the T type, which is the receiver itself;


3. Anonymous function type analysis


Continue to analyze the parameter T.() of the apply function -> Lambda expression block of Unit type . This Lambda expression has no return value.

The function type is (parameter type list) -> return value type , such as:

  • () -> Unit type represents a function with empty parameters and empty return value;
  • () -> String type means that the parameter is empty and the return value type is a function of String type;
  • (Int) -> String type represents a function whose parameters are of Int type and return value type is of String type;

You can refer to the [Kotlin] Kotlin function summary (named function | anonymous function | lambda expression | closure | inline function | function reference) blog for understanding;


If the generic extension function is:

fun <T> T.apply(block: () -> Unit): T

It is easy to understand. If you remove the T. in the parameter type T.() -> Unit , the above function receives a Lambda expression with empty parameters and empty return value;


4. Review of extension functions


Recall extension functions and define extension functions for existing classes , such as: define extension functions for String;

In the following code, String.addStr adds an extension function addStr to the String type;

/**
 * 为 String 定义扩展函数, 拼接原字符串和扩展函数参数, 并将结果返回
 */
fun String.addStr(str: String): String {
    
    
    println("this = $this, string = $str")
    return this + str
}

fun main() {
    
    
    println("123".addStr("abc"))
}

Refer to the [Kotlin] summary of extension functions (superclass extension function | private extension function | generic extension function | extended attribute | define extension file | infix keyword usage | rename extension function | Kotlin standard library extension function) blog to understand ;


5. Generic extension function function type


Adding extension functions to generics is called generic extension functions, and the format is:

fun <T> T.函数名(参数列表): T {
    
    
	函数体
}

For example: add the extension function addStr to the generic T. It has no parameters and no return value, that is, it returns the Unit type return value. The code is as follows:

fun <T> T.addStr(): Unit {
    
    
	//函数体
}

The type of this generic extension function is the Lambda expression parameter type of the apply function T.() -> Unit;


Refer to the [Kotlin] summary of extension functions (superclass extension function | private extension function | generic extension function | extended attribute | define extension file | infix keyword usage | rename extension function | Kotlin standard library extension function) blog to understand ;


6. Generic extended anonymous functions


Extension functions and anonymous functions can be combined; extension functions can also be anonymous functions, and anonymous functions can also be extension functions;

T.() -> The function type of Unit is a generic extended anonymous function , which is an extension function defined for generics, and the extension function is an anonymous function;


The anonymous function corresponds to the named function , the extension function corresponds to the original function , and the generic corresponds to the specific type , so the three can be combined in any way;


This anonymous function type T.() -> Unit has three layers of BUFF;

  • Generics
  • extension function
  • anonymous function

Generic extension function anonymous function T.() -> Unit evolution path:

  • Ordinary anonymous function: () -> Unit , the parameters and return value of this function are empty;
  • Extended anonymous function: String.() -> Unit , this function is an extension function defined for the specific String type;
  • Generic extended anonymous function: T.() -> Unit , this function is a generic extended anonymous function defined for all types, and all classes can call this anonymous function;

7. Apply standard library function parameter analysis


Return to the apply standard library function again and analyze its function prototype:

public inline fun <T> T.apply(block: T.() -> Unit): T

The parameter of this function is a Lambda expression/anonymous function/closure , of type T.() -> Unit. This is a generic extended anonymous function type, an extension function defined for generic T, and T is also the receiver. type, return type;


Comparison between generic extension function anonymous function and ordinary anonymous function

Comparison between anonymous functions of the generic extended function type and ordinary anonymous functions: The apply function passes in the parameters of the generic extended anonymous function type T.() -> Unit, instead of passing in an ordinary anonymous function () -> Unit;

  • Anonymous function of generic extension function type: What is passed in is the anonymous function of generic extension function type T.() -> Unit . In this Lambda expression, the this keyword can be used to access the receiver, and the receiver can be called directly member properties and member methods;
  • Ordinary anonymous function: If an ordinary anonymous function is passed in , the this keyword cannot be used in the function to access the receiver, and the receiver must be accessed as an external variable;

Assumption that the apply function parameter is not a generic extended function type

If you want to achieve the above effect of calling the receiver through this in a Lambda expression without using a generic extension function , then you need to use an anonymous extension function of a common type;

For example: if you want to call the receiver through this in the closure parameter of the apply extension function of String type, you must use the following standard library function;

public inline fun String.apply(block: String.() -> Unit): String

Once the above code style is written, only the String type can call the apply function, and other types cannot call the function;





3. Code examples




1. Custom apply function receives ordinary anonymous function parameters


Use this keyword to report an error

Code example: In the following code, the parameter of the apply function is () -> Unit type , which is an ordinary anonymous function , and this cannot be called in this closure;

public inline fun <T> T.apply(block: () -> Unit): T {
    
    
    println("调用普通匿名函数")
    block()
    return this
}

fun main() {
    
    
    "123".apply {
    
    
        println(this)
    }
}

Once this is called, an error will be reported during compilation, prompting the following error:

'this' is not defined in this context

Insert image description here


Calling external variables using variable names

In this case, external variables can only be called through the variable name in the anonymous function ;

Code example:

public inline fun <T> T.apply(block: () -> Unit): T {
    
    
    println("调用普通匿名函数")
    block()
    return this
}

fun main() {
    
    
    var str = "123"
    str.apply {
    
    
        println(str)
    }
}

Execution result: print this, you can print the receiver directly;

调用普通匿名函数
123

Insert image description here


2. Custom apply function receives extended anonymous function parameters


Code example: If you want to use the this keyword to access the receiver in an anonymous function, it must be defined as an extension function;

public inline fun String.apply(block: String.() -> Unit): String {
    
    
    println("调用扩展匿名函数")
    block()
    return this
}

fun main() {
    
    
    var str = "123"
    str.apply {
    
    
        println(this)
    }
}

Results of the :

调用扩展匿名函数
123

Insert image description here


3. Custom apply function receives generic extended anonymous function parameters


Code example: In the following code, the apply function is customized, which receives an anonymous function parameter of the generic extension function type , of type T.() -> Unit . When calling, it can be used in the Lambda expression of the apply function. Use this to call the receiver;

public inline fun <T> T.apply(block: T.() -> Unit): T {
    
    
    println("调用自定义泛型扩展函数")
    block()
    return this
}

fun main() {
    
    
    "123".apply {
    
    
        println(this)
    }
}

Execution result: print this, you can print the receiver directly;

调用自定义泛型扩展函数 :
123

Insert image description here

Guess you like

Origin blog.csdn.net/han1202012/article/details/128759825