Higher-order functions and Lambda

Higher-order functions are functions that use functions as arguments or return values.

Example:

// function as argument
fun test1(f: () -> Unit) {}// function as return value
fun test2(other1: Int, other2: Int): () - >Int = {other1 + other2}
Copy the code

T.() ->Unit and () ->Unit in Kotlin’s function arguments

The former is a function type with a receiver, and a member of the modified instance can be called inside the function, which is accessed through this. The latter is a higher-order function with no receiver.

Examples are as follows:

We extend two functions, test1 and test2, for the SharedPreferences type

fun <T: SharedPreferences> T.test1(f: T. () -> Unit) {}fun <T: SharedPreferences> T.test2(f: () -> Unit) {}Copy the code

When the call is made:

Summary: Inside the literal of a function specifying the receiver type, the receiver object passed to the caller becomes implicit this so that members of the receiver object can be accessed without any additional qualifiers. The receiver object can also be accessed using this expression.

Lambda

fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor. () -> Unit
) {
    val editor = edit()
    action(editor)
    if (commit) {
        editor.commit()
    } else {
        editor.apply()
    }
}

private const valKEY_TOKEN = "token"class PreferencesManager(private val preferences: SharedPreferences){
    fun saveToken(token: String) {
        preferences.edit { putString(KEY_TOKEN, token) }
    }
}

Copy the code

Java code decompiled from bytecode:

// Bytecode decompiled
/* Copyright 2020 Google LLC.spdx-license-Identifier: Apache-2.0 */
public final void saveToken(@NotNull final String token) {

    // The extension method we defined to modify SharedPreferences is called
    PreferenceManagerKt.edit$default(
        this.preferences, // SharedPreferences instance object
        false.// The default value of the commit tag
        (Function1)(new Function1() { // A new Function object is created for the action argument
            // $FF: synthetic method
            // $FF: bridge method
            public Object invoke(Object var1) {
                this.invoke((Editor)var1);
                return Unit.INSTANCE;
            }
            public final void invoke(@NotNull Editor $this$edit) {
                Intrinsics.checkParameterIsNotNull($this$edit, "$receiver");
                $this$edit.putString("token", token); // The implementation in our action parameter}}),1, (Object)null);
}
Copy the code

Each higher-order function incurs additional runtime overhead by creating function objects and allocating memory

Inline function inline

Reduce the creation of function objects

The contents of a Lambda expression create a function object implemented by default for an anonymous inner class at compile time

Use the inline keyword to reduce creation of function objects:

inline fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor. () -> Unit) {... }Copy the code

Java code decompiled from bytecode:

// Bytecode decompiled
/* Copyright 2020 Google LLC.spdx-license-Identifier: Apache-2.0 */
public final void saveToken(@NotNull String token) {
  // The contents of the SharedPreferences. Edit function
  SharedPreferences $this$edit$iv = this.preferences;
  boolean commit$iv = false;
  int $i$f$edit = false;
  Editor editor$iv = $this$edit$iv.edit();
  Intrinsics.checkExpressionValueIsNotNull(editor$iv, "editor");
  int var7 = false;
  
  // What is implemented in the action argument
  editor$iv.putString("token", token);
  
  // The contents of the SharedPreferences. Edit function
  editor$iv.apply();
}
Copy the code

Because of the inline keyword, the compiler copies the contents of the inline function to the call, avoiding the creation of a new function object.

#### where to use inline

Attempting to mark a function that does not receive a function as an argument as an inline function does not give you a performance boost, because the essence of an inline function is to substitute code for object creation for performance gains. Because inline increases code generation, avoid inlining large functions, which are mostly 1 to 3 lines in the Kotlin library.

The first case: there are multiple function arguments in a function.

** The second case: the ** function takes only one function as an argument, so don’t use inline at all. If you wanted to use the inline keyword, you would have to mark the parameter as noinline, but the performance advantage of inlining this method would be minimal.

noinline

An inline function cannot hold a reference to an passed function argument object, nor can it pass an passed function argument object to another function. The noinline keyword is therefore needed to mark the corresponding function object, which will still be instantiated at compile time.

fun myFunction(importantAction: Int. () -> Unit) {
    importantAction(- 1)}inline fun SharedPreferences.edit(
    commit: Boolean = false.// Use the noinline keyword here
    noinline importantAction: Int. () -> Unit = { },
    action: SharedPreferences.Editor.() -> Unit) { myFunction(importantAction) ... }...fun saveToken(token: String) {
    var dummy = 3
    preferences.edit(importantAction = { dummy = this}) {
         putString(KEY_TOKEN, token)
    }
}
Copy the code

Take a look at the Java code decompiled from bytecode:

 /* Copyright 2020 Google LLC.spdx-license-Identifier: Apache-2.0 */
public final void saveToken(@NotNull String token) {
   // The saveToken method
   final IntRef x = new IntRef();
   x.element = 3;
   
   // Inline the edit method
   SharedPreferences $this$edit$iv = this.preferences;
 
   // noinline Function declaration causes new Function to be called
   Function1 importantAction$iv = (Function1)(new Function1() {
        // $FF: synthetic method
        // $FF: bridge method
        public Object invoke(Object var1) {
            this.invoke(((Number)var1).intValue());
            return Unit.INSTANCE;
        }
        public final void invoke(int $receiver) {
            // The saveToken functionx.element = $receiver; }});// Inline the edit method
   boolean commit$iv = false;
   int $i$f$edit = false;
   PreferenceManagerKt.myFunction(importantAction$iv);
   Editor editor$iv = $this$edit$iv.edit();
   Intrinsics.checkExpressionValueIsNotNull(editor$iv, "editor");
   int var9 = false;
   editor$iv.putString("token", token);
   editor$iv.apply();
}
Copy the code

You can see that after the noinline keyword is added, the compiler creates the function object in the form of an anonymous inner class. Others that are not marked noinline still copy the inline function to the call.

A combination of the higher-order function Lambda with inline

Each function in a higher-order function is an object and captures a closure, which is the external scope variable that is accessed within the function body.

Lambda expressions cause additional memory allocation, so inline can be used to save memory.

Refer to the reference

  • Inline function in chapter references since Google developers, public Kotlin Vocabulary | the principle and application of the inline function