Deep understanding of Kotlin higher-order functions, anonymous functions and lambda expressions

Before we talk about higher-order functions, we have to know what function types are, ah, what? Don't know the function type? Well, let's talk about what is a function type.

1, Function type

Defining variable types in Kotlin is very simple, as follows:

var a :Int = 1 // Int type variable
var person :Person = Person()  // Define a Person variable

So what should function types be? Yes, in the following form:

(X, y) - > Z, where X and Y represent the parameters of the function. Of course, the number of parameters is arbitrary, only 2 are written here, and Z represents the return value of the function.

After knowing this form, we can quickly write out the function types as follows:

var f :(Int , Int)-> Int

(Int, Int) - > Int means that the parameters of the function are 2 Int types and the return value is Int type.

Note: the parentheses in the parameter list are required. Even if there are no parameters, they cannot be omitted, that is, they are written as () - > string; Even if the return value type of the function type is Unit, Unit cannot be omitted and written, for example: (int) - > Unit.

How are function types assigned?

Suppose we define the function type as follows:

var f :(Int , Int)-> Int

Then a sum function is defined as follows:

fun sum (a :Int, b :Int) : Int {
   return a + b
}

It can be seen that the signature and return value types of the function type f and sum functions defined above are the same.
So can we directly assign values like this?

f = sum

This will not work. What is the correct way to write it?

f = ::sum
What is the name of double colon (::) plus square method?

Kotlin's official statement: the writing of double colons is called Function Reference.
But what does that mean? Indicates that it points to the above function? Since they are all one thing, why not write the function name directly and add two colons?

Because of the addition of two colons, this function becomes an object, and only an object can be assigned to a variable.
But kotlin's function itself cannot be treated as an object. Then what shall I do? Kotlin's choice is to create an object with the same function as the function. How to create? Use double colons.

In Kotlin, if you add a double colon to the left of a * * function name, it does not represent the function itself, but an object, or a reference to the object. However, the object is not the function itself, but an object with the same function as the function.

How to use it?
val f = ::sum
val res1 = f(2, 3) // Output 5
(::sum)(2, 3) // Output 5

Here's another thing:

f.invoke(2, 3) // Output 5

So you can call invoke() on an object of function type, but you can't do this on a function:

sum.invoke(2, 3) // An error is reported, because only objects of function type have this built-in invoke() that can be used

Let's start with what is a high-order function

2, High order function

What is a high-order function: a function whose parameter or return value is a function type. The higher-order functions are as follows:

// Where the parameter opt is a function type
fun f1(name :String, opt :(Int, Int) ->Int) :String {
    return name + opt.invoke(2, 3)
}

// The return value of the function is the function type
private fun f2 () : (Int, Int) ->String {
    return ::sum
}
Why use higher order functions?

In Java, I want to pass a method as a parameter to another method, OK?
The answer is no, but interfaces can be used instead in Java:

interface OnReporterListener {
   void onReporter(String msg);
}

void report(OnReporterListener listener) {
   listener.onReporter("Ha ha, little pig");
}

However, with the existence of higher-order functions in Kotlin, it can be easily realized

private fun report(opt : (String) -> Unit) {
    opt.invoke("Ha ha, little pig")
}
// Similarly, define a function whose front value is consistent with the return value
private fun doReport(msg :String) {
    Log.e("xxx", "msg = $msg")
}

The test code can be written as follows:

private fun test() {
    report(::doReport)
}

It's too troublesome. You have to define a function first, and then pass it in the form of:: function name. In fact, there are simpler ways. Let's talk about anonymous functions and lambda expressions.

3, Anonymous function

Anonymous function: refers to a function without a name. How to call a function without a name? The answer is that you can't call it directly. You can assign an anonymous function to a variable or pass it as a function parameter.

Anonymous functions are as follows:

fun(a: Int, b: Int) = a + b

The above anonymous functions cannot be called directly and can be assigned to variables:

val f = fun(a :Int, b :Int) = a + b
f(2, 3)

Note: anonymous functions are essentially function type objects, so they can be assigned to variables.

Therefore, the above test method report (: doreport) can be changed as follows:

We directly pass in the anonymous function, which is equivalent to directly assigning the anonymous function to the parameter of the function type:

private fun test() {
    //  report(::doReport)
    // The String can be omitted, because the input parameter of the passed in function type is String from the report method
    report(fun(msg :String) {
        Log.e("xxx", "msg = $msg")
    })
}

If we assign an anonymous function to the variable opt, then opt is a function type

private fun test() {
     // report(::doReport)
     // The String can be omitted, because the input parameter of the passed in function type is String from the report method
    val opt = fun (msg :String) { 
        Log.e("xxx", "msg = $msg")
    }
    report(opt)
}

Note: if the anonymous function can infer the function parameter type from the variable type on the left, the parameter type of the anonymous function can be omitted, as follows:

// You can write it in the following way, because the variable on the left indicates the parameter type
val noNameFun3: (Int, Int) -> Int = { a, b ->
    a + b
}

The following cannot be omitted:

// The parameter type of the input parameter cannot be omitted because the input parameter type cannot be inferred from elsewhere
// The return value type can be omitted because it can be inferred
val noNameFun2 = { a: Int, b: Int -> 
    a + b
}

As you can see from the above, it seems that a function is really passed in. In fact, what you pass is an object, an object of function type. Because anonymous function is not a function, but an object of function type.

This way of writing is still not concise enough. Is there a more concise way? Of course, that's the lambda expression.

4, lambda expression

In fact, the essence of Lambda expression is anonymous function, and the essence of anonymous function is function type object. Therefore, Lambda expressions, anonymous functions and double colons + function names are all function type objects, which can be assigned to variables and passed as function parameters.

The format of lambda expression is as follows:
Lambda expressions are surrounded by curly braces {}
The parameter of Lambda expression is on the left side of - > if there is no parameter, only the function body is reserved
The function body of Lambda expression is after - > function
The return value type of Lambda expression is the return value type of the last line of code in the function body

  1. Expressions with parameters and return values:
val lambda1 = {a :Int, b :Int ->
    a + b
}
  1. With parameters and no return value
val lambda2 = { name : String ->
    Log.e("xx", "name = $name")
}
  1. No parameter no return value
val lambda3 = {
    Log.e("xx", "No parameter no return value lambda expression")
}

Define a higher-order function as follows:

fun test (name :String , opt : (a :Int , b: Int) -> Int) :String {
    return name + opt.invoke(3, 4)
}

Then we call it in the form of lambda expression, as follows:

test("lambda", {a, b -> a*b})

Note: since the lambda expression is the last parameter of the function test, you can move the lambda expression outside, as follows:

test("lambda") { a, b -> 
    a * b
}

If a function receives only one function type parameter, the parentheses of the function call can be removed when passing in a Lambda expression:

 test3 { a, b ->
     a + b
 }

fun test3 (opt : (a :Int , b: Int) -> Int) :String {
    return opt.invoke(3, 4).toString()
}

If the function type has only one input parameter, its parameter is also omitted and not written:

test4 { 
    val value = it // Use it to represent unique parameters
    Log.e("xxx", "it = $it")
}

fun test4(opt : (String) -> Unit) {
    opt.invoke("123")
}

5, Summary:

This paper introduces the nature of function types, higher-order functions, anonymous functions and Lambda expressions:

  1. Functions cannot be passed directly through function names, but can be passed through double colons plus function names
  2. Double colon + function name, anonymous function and Lambda expression are actually function type objects
  3. Higher order functions are just parameter lists or return value types. Functions with function types are nothing special

Keywords: kotlin

Added by Tomcat13 on Wed, 13 Oct 2021 20:04:37 +0300