Modifier source code, true for Kotlin higher-order functions 6

Modifier source code, true for Kotlin higher-order functions 6

In JetPack Compose, Modifier is often used, which can control the behavior and appearance of components such as size, background, etc., and can also add some interactions, such as clicking, sliding, etc. The Modifier function is very powerful. The core code of Modifier is less than 100 lines. The high-level functions of Kotlin are used in Modifier so that it is a bit confusing at first glance. So I plan to write this article to learn how the code in Modifier is executed. The core source code of Modifier is as follows: Modifier.kt

interface Modifier { fun <R> foldIn (initial: R , operation: ( R , Element ) -> R ) : R fun <R> foldOut (initial: R , operation: ( Element , R ) -> R ) : R fun any (predicate: ( Element ) -> Boolean ) : Boolean fun all (predicate: ( Element ) -> Boolean ) : Boolean infix fun then (other: Modifier ) : Modifier = if (other === Modifier) this else CombinedModifier( this , other) interface Element : Modifier { override fun <R> foldIn (initial: R , operation: ( R , Element ) -> R ) : R = operation(initial, this ) override fun <R> foldOut (initial: R , operation: ( Element , R ) -> R ) : R = operation( this , initial) override fun any (predicate: ( Element ) -> Boolean ) : Boolean = predicate( this ) override fun all (predicate: ( Element ) -> Boolean ) : Boolean = predicate( this ) } companion object : Modifier { override fun <R> foldIn (initial: R , operation: ( R , Element ) -> R ) : R = initial override fun <R> foldOut (initial: R , operation: ( Element , R )- > R ) : R = initial override fun any (predicate: ( Element ) -> Boolean ) : Boolean = false override fun all (predicate: ( Element ) -> Boolean ) : Boolean = true override infix fun then (other: Modifier ) : Modifier = other override fun toString () = "Modifier" } } class CombinedModifier ( private val outer: Modifier, private val inner : Modifier ): Modifier { override fun <R> foldIn (initial: R , operation: ( R , Modifier . Element ) -> R ) : R = inner .foldIn(outer.foldIn(initial, operation), operation) override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R = outer.foldOut( inner .foldOut(initial, operation), operation) override fun any (predicate: ( Modifier . Element ) -> Boolean ) : Boolean = outer.any(predicate) || inner .any(predicate) override fun all (predicate: ( Modifier . Element ) -> Boolean ) : Boolean = outer.all(predicate) && inner .all(predicate) override fun equals (other: Any ?) : Boolean = other is CombinedModifier && outer == other.outer && inner == other. inner override fun hashCode () : Int = outer.hashCode() + 31 * inner .hashCode() override fun toString () = "[" + foldIn( "" ) {acc, element -> if (acc.isEmpty()) element.toString() else " $acc , $element " } + "]" } Copy code

The code structure in the Modifier.kt file is as follows

Element is an abstract interface for Modifier elements. **Modifier ** provides a static singleton class that implements the Modifier interface. CombinedModifier combines two Modifiers, which is an important key to generate Modifier chain.

Next, let s focus on

then
foldIn
foldOut
3.functions

fun foldIn (initial: R , operation: ( R , Element ) -> R ) : R fun <R> foldOut (initial: R , operation: ( Element , R ) -> R ) : R Infix Fun the then (OTHER: Modifier ) : Modifier = IF (=== OTHER Modifier) the this the else CombinedModifier ( the this , OTHER) copy the code

In order to facilitate the explanation, a few simplified versions of Modifier implementation classes and some functions are provided.

class SizeModifier : Modifier.Element { override fun toString () = "SizeModifier" } class PaddingModifier : Modifier.Element { override fun toString () = "PaddingModifier" } class OffsetModifier : Modifier.Element { override fun toString () = "OffsetModifier" } Fun Modifier. size () = the this .then (SizeModifier ()) Fun Modifier. padding () = the this .then (PaddingModifier ()) Fun Modifier. offset () = the this .then (OffsetModifier ()) copying the code

then function

The then method is an important function to generate the Modifier chain.

fun main () { val modifier = Modifier.size().padding().offset() println(modifier) } ---------Output--------------- [SizeModifier, PaddingModifier, OffsetModifier] Copy code

Put below

Modifier.size().padding().offset()
The process of single-step decomposition of Modifier generation chain

first step

Val modifier1 = Modifier duplicated code

This Modifier is the Modifier of that static singleton, not an interface.

2.step

val modifier2 = modifier1.size() Copy code

According to the definition of the size function above =>

val modifier2 = Modifier.then(SizeModifier())

Then according to the then function of the static singleton Modifier

companion object : Modifier { override infix fun then (other: Modifier ) : Modifier = other } Copy code

val modifier2=SizeModifier()
Good guys, Modifier s then method is not "what you eat and what you vomit", so modifier2 is
SizeModifier
Object

third step

val modifier3 = modifier2.padding() Copy code

According to the definition of padding function =>

modifier3=modifier2.then(PaddingModifier())

Because modifier2 is a SizeModifier object, SizeModifier implements Modifier.Element, so look at the function definition of Modifier.Element then and find that the then function is not rewritten, that is, the default then function of the Modifier interface is executed.

Infix Fun the then (OTHER: Modifier ) : Modifier = IF (=== OTHER Modifier) the this the else CombinedModifier ( the this , OTHER) copy the code

Obviously if the condition is not met, I left the else and combined SizeModifier and PaddingModifier.

=>

modifier3 = CombinedModifier(modifier2, PaddingModifier())

=>

modifier3 = CombinedModifier(SizeModifier(), PaddingModifier())

the fourth step

Val modifier4 = modifier3.offset () to copy the code

This step is similar to modifier3, so

//modifier4 => CombinedModifier(outer = modifier3, inner = OffsetModifier()) //modifier4 => CombinedModifier(outer = CombinedModifier(modifier2, PaddingModifier()), inner = OffsetModifier()) //modifier4 => CombinedModifier(outer = CombinedModifier (SizeModifier (), PaddingModifier ()), Inner = OffsetModifier ()) copying the code

In the end modifier4 got something like a Russian doll CombinedModifier(outer = CombinedModifier(SizeModifier(), PaddingModifier()), inner = OffsetModifier())

Summarize with two pictures below

foldOut function

With the analysis of the generated chain above, let's talk about how to traverse the elements of this chain. The foldOut function is to give an initial value, and then calculate the value according to the elements on the Modifier chain according to the operation function logic we passed in and return it.

fun main () { val modifier = Modifier.size().padding().offset() val result = modifier.foldOut( "start---" ) {mod, str -> println(mod) " $str $mod " } print( "result = $result " ) } ------------------Output---------------- OffsetModifier PaddingModifier SizeModifier result=start---OffsetModifier PaddingModifier SizeModifier Copy code

It's amazing, foldOut can pass all the elements in the Modifier chain. How does it do it? In order to facilitate the analysis, let's modify the above code

fun main () { // Code 0 val modifier = Modifier.size().padding().offset() val initial = "start---" val operation = {mod:Modifier, str:String -> println(mod) " $str $mod " } val result = modifier.foldOut(initial,operation) Print ( "Result = $ Result " ) copy the code

According to the analysis of the then function of Modifier above, we know the process of forming a chain. From the above analysis shows that the code modifier is CombinedModifier 0 type, the following form in FIG 2-1

Now that I know it is

CombinedModifier
Then take a look at how its foldOut is implemented

override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R = outer.foldOut ( Inner .foldOut (Initial, Operation), Operation) copy the code

It looks very simple with just two lines of code, but it does a lot of things. Let's transform the above function equivalently.

override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R { //1 First execute the inner foldOut function val nextInitial:R= inner .foldOut(initial, operation) //2 Use the return value of inner foldOut as the initial parameter of outer foldOut function return outer.foldOut(nextInitial, operation) } Copy code

The first step is to execute the inner foldOut function. As shown in Figure 2-1, the inner is an object of type OffsetModifier, and OffsetModifier eventually inherits Modifier.Element. Its foldOut function is implemented as follows

override fun <R> foldOut (initial: R , operation: ( Element , R ) -> R ) : R = Operation ( the this , Initial) copy the code

This function calls the operation function we passed in, and passes in the current object and the passed intial as parameters. At this time, the code in the operation function body we wrote is executed for the first time. According to the above analysis, we can see that the first step is

 val nextInitial:R=inner.foldOut(initial, operation)
The return value is the return value of the operation function, which is
"``$``str``$``mod ``"
The value at this time
start---OffsetModifier
When the parameters are ready, we are ready to execute the second step. In the second step, the outer foldOut function is executed. According to Figure 2-1, we know that the outer is of the CombinedModifier type at this time, and its foldOut function still executes its inner in the first step. At this time, its inner is the object of SizeModifer, and its type is still Modifier.Element. Similar to the above, the code in the operation function body is executed for the second time. Next, the outer foldOut function that executes it Now, its outer is a SizeModifer object and it is a Modifier.Element, so the code in the operation function body is executed for the third time.

Let's use a piece of pseudo code to demonstrate the execution flow of foldOut

//0 CombinedModifier's foldOut function is divided into two steps. Override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R { //1 First execute inner foldOut function val initial1:R= inner .foldOut(initial, operation) //2 Use the return value of the inner fodOut as the initial parameter of the outer foldOut function return outer.foldOut(initial1, operation) } //1 If inner is of Modifier.Element type, //According to the above analysis, the inner foldOut function is actually the operation function override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R { //At this time, operation is executed val initial1:R=operation( inner ,initial) return outer.foldOut(initial1, operation) } //2 Then execute the outer foldOut, if it is still CombinedModifier, then repeat the above steps override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R { val initial1:R =operation( inner ,initial) //Assuming that outer can directly access its inner and outer val initial2=outer. inner .foldOut(initial1, operation) return outer.outer.foldOut(initial2, operation) } //3 If outer.inner is of Modifier.Element type, the same is true to override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R { val initial1:R=operation( inner ,initial) val initial2:R=operation(outer. inner ,initial1) return outer.outer.foldOut(initial2, operation) } //4 Until the end outer is not a CombinedModifier type end override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R { val initial1:R=operation( inner ,initial) val initial2:R=operation(outer. inner ,initial1 /*return value of previous step*/ ) ... val initialN:R=operation(outer.outer.......outer. inner ,initialN_1 /*return value of previous step*/ ) return operation(outer.outer.........outer,initialN ) } Copy code

summary: The foldOut function of CombinedModifier executes its inner foldOut first, then executes the outer foldOut function, and takes the inner foldOut return value as the parameter of the outer foldOut function

foldIn function

The foldIn function is similar to the foldOut function. It also requires an initial value and an operation function, and returns the result according to the Modifier chain calculation. But there are some differences between the two. Here is a simple example of foldIn

val modifier = Modifier.size().padding().offset() val result = modifier.foldIn( "start----" ) {str, mod -> println(mod) " $str $mod " } print( "result = $result " ) ------------------Output---------------- SizeModifier PaddingModifier OffsetModifier result=start----SizeModifier PaddingModifier OffsetModifier Copy code

Code 3-1 It can be seen from the output result that the execution order of foldIn and foldOut are opposite. CombinedModifier's foldIn function

override fun <R & lt> foldin (Initial: R & lt , Operation: ( R & lt , Modifier . the Element ) -> R & lt ) : = R & lt Inner .foldIn (outer.foldIn (Initial, Operation), Operation) copy the code

This function can still be broken down into two steps

override fun <R> foldIn (initial: R , operation: ( R , Modifier . Element ) -> R ) : R{ //1. Execute the outer foldIn function val result1=outer.foldIn(initial, operation) //2 Take the return value of the outer foldIn function as the parameter of the inner foldIn val result0 = inner .foldIn(result1, operation) return result0; } Copy code

For code 3-1, we can expand according to the above function

//val modifier = Modifier.size().padding().offset() // is equivalent to => val modifier1 = Modifier.size() val modifier2 = CombinedModifier( inner = PaddingModifier(), outer = modifier1) val modifier3 = CombinedModifier( inner = OffsetModifier(), outer = modifier2) //val result = modifier.foldIn("start----"){....} // is equivalent to => val initial = "start---" val operation = {str: String, mod : Modifier -> println(mod) " $str $mod " } //0 modifier3.foldIn(initial, operation) Copy code

According to the foldIn function of CombinedModifier, the code 0 is equivalent to

//1 val result1=modifier2.foldIn(initial,operation) val result0=OffsetModifier().foldIn(result1,operation) return result0 duplicated code

Because modifier2 is still code CombinedModifier type, code 1 is equivalent to

val result2=modifier1.foldIn(initial,operation) val result1=PaddingModifier().foldIn(result2,operation) val result0=OffsetModifier().foldIn(result1,operation) return result0 duplicated code

Code 3-2 modifier1 is a SizeModifier object, which is of type Modifier.Element. The foldIn function of Modifier.Element is as follows

override fun <R> foldIn (initial: R , operation: ( R , Element ) -> R ) : R = operation(initial, this ) copy the code

This function is very simple to execute the operation function we passed in. PaddingModifier and OffsetModifier are also Modifier.Element types, so **Code 3-2** is equivalent to

val result2 = operation(initial, SizeModifier()) val result1 = operation(result2, PaddingModifier()) val result0 = operation(result1, OffsetModifier()) return result0 duplicated code

It can be seen that foldIn finally executes the operation function we passed in according to the Modifier chain. The result of each operation is used as the initial parameter of the next operation function.

summary

Modifier combines the two Modifiers into a CombinedModifier through the then function. The inner points to the Modifier corresponding to the current node, and the outer points to the Modifier of the next node. If the outer is also a CombinedModifier, then this Modifier can continue to extend.

foldOut & foldIn are the same: given an initial value, return a calculated value. It will traverse every element in the execution Modifier chain. Difference: the order of traversal of the two functions is different. foldOut is executed from back to front according to the order of Modifier chain addition, and foldIn is executed from front to back according to the order of Modifier chain addition.