Higher-order functions, anonymous functions, and lambda expressions

Kotlin functions are first-class, which means they can be stored in variables and data structures, passed as arguments to other higher-order functions, and returned from other higher-order functions. Functions can be manipulated just like any other non-function value.

First-class function: A first-class function is a function that is treated as a first-class citizen in a programming language. This means that functions can be assigned to variables or stored in data structures as arguments to other functions, as return values of functions.

Higher-order functions

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

 //learnHighFun is a higher-order function because it takes a function type argument, funParam. Note that there is a new noun, function type. Functions are also a type in Kotlin. What kind of function is it? Note that (Int) ->Int is a function that takes an Int and returns an argument of type Int.
 fun learnHighFun(funParam:(Int) - >Int,param:Int){}
Copy the code

So that’s the simplest higher-order function. Before we get to higher-order functions, it’s obviously worth looking at the new term above, function types

Function types

How do I declare parameters of a function type

In Kotlin, it’s very easy to declare the format of a function type. In Kotlin, we use the -> symbol to organize the parameter type and the return value type. The left and right sides are the parameters of the function, and the right side is the return value of the function. As follows:

 // represents the function type, takes an argument of type Int and returns a value of type Int
 (Int) - >Int
 ​
 // Represents the function type. It takes two arguments, one Int and one String, and returns an Int
 (Int,Stirng)->Int
Copy the code

How do you declare a function with no function parameters and no return value? As follows:

 // Declare a function type that takes no arguments and returns an Int. () cannot be omitted if the function takes no arguments() - >Int
 ​
 // Specify a function type that has no arguments and no return value. In function types, Unit cannot be omitted when the function has no return value() - >Unit
 ​
Copy the code

So that’s the simple declaration of function types. So if it’s a higher-order function, and its argument type is also a higher-order function, how do you declare it? For example, what does this mean?

 private fun learnHigh(funParams:((Int) - >Int) - >Int){}
 // This is a higher-order function, learnHigh, which takes a function type argument, funParams. The funParams type is also the type of a higher-order function. FunParams is a function type that takes an argument of a normal function class (Int)->Int and returns an Int. This is really confusing to read, but once you understand this complicated example, you should be able to make sense of almost all higher-order functions.
 ​
 // The highParam type matches the type of the learnHigh function received above
 fun highParam(param: (Int) - >Int):Int{
     return  1
 }
Copy the code

Higher-order functions whose arguments are function types and whose return values are function types are basically the same as above. So the next question is, I’ve talked about so many higher-order functions, so many types of functions. So how do you pass the parameters of these function types? In other words, how do you pass the arguments of these function types to higher-order functions? Can I just use the function name? Obviously not, because the function name is not an expression and has no type information. Then we need a pure method reference expression.

Function reference

In Kotlin, two colons are used to implement a reference to a method of a class. What information does this sentence contain? First, since it is a reference, the specification is an object. That is, a reference implemented with a double colon is also an object. It is an object of type function. Second, since the object needs to be created, that is, an object of type function is created. This object has the same function as the function. Here are some examples of what these two sentences mean:

 fun testFunReference(a){
     funReference(1)  // A normal function, called directly by the function name followed by arguments.
     val funObject = ::funReference // A reference to a function that is essentially an object
     testHighFun(funObject) // Pass an object of this type to a higher-order function via a function reference. So the arguments received in higher-order functions are essentially objects.
 ​
     funObject.invoke(1) // equivalent to funReference(1)
     funObject(1) // Invoke funReference(1), invoke funobject.invoke (1)
 }
 ​
 fun funReference(param:Int){
     //doSomeThing
 }
 ​
 fun testHighFun(funParam:(Int) - >Unit){
     //doSomeThing
 }
Copy the code
 // This is the decompiled Java code
 public final void testFunReference(a) {
     this.funReference(1);
     //val funObject = ::funReference
     KFunction funObject = new Function1((TestFun)this) {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1) {
             this.invoke(((Number)var1).intValue());
             return Unit.INSTANCE;
         }
 ​
         public final void invoke(int p1) {
             ((TestFun)this.receiver).funReference(p1); }};this.testHighFun((Function1)funObject);
     ((Function1)funObject).invoke(1);
     ((Function1)funObject).invoke(1);//funObject(1) finally calls funobject.invoke (1)
 }
 ​
 public final void funReference(int param) {}// testHighFun receives an object of type Function1
 public final void testHighFun(@NotNull Function1 funParam) {
     Intrinsics.checkNotNullParameter(funParam, "funParam");
 }
Copy the code

That’s it for function references.

I understand the above usage, but it seems to require a function to be declared each time. Are there other ways to call higher-order functions that do not require a function to be redeclared? Well, it’s definitely there, and if it doesn’t support Kotlin’s higher-order function, isn’t that a little bit of a bug? Let’s move on to the other two, anonymous functions in Kotlin and Lambda expressions.

Anonymous functions

Let’s talk about an anonymous function, which by definition is a function without a name, and notice that the word ‘function’ is in quotes. Let’s first see how it works in higher-order functions.

 // Continue with the example above
 // Instead of calling testHighFun(funObject) via a reference object, you can take a function directly as an argument to this higher-order function.
 val param = fun (param:Int){ // Note that there is no function name, so it is anonymous' function '.
     //doSomeThing
 }
 testHighFun(param)
Copy the code

Note: The function testHighFun receives a reference to a function object, meaning that val Param is a reference to a function object. The anonymous’ function ‘fun(param:Int){}, Its essence is a function object. It’s not a function. We can take a look at the decompiled Java code

 Param is a reference to an object of type Function1
 Function1 param = (Function1)null.INSTANCE;
 this.testHighFun(param);
Copy the code

So remember, Kotlin’s anonymous function, it’s not a function by nature. It’s the object. It’s not the same thing as a function, it’s an object of type function. Objects and functions, they’re two different things.

Lambda expressions

The full syntax of a Lambda expression is as follows:

 val sum: (Int.Int) - >Int = { x: Int, y: Int -> x + y }
Copy the code

Lambda expressions are always enclosed in curly braces, full syntactic parameter declarations are enclosed in curly braces, with optional type annotations, and the function body is followed by a -> symbol. If the inferred return type of the Lambda is not Unit, then the last (or possibly single) expression in the body of the Lambda is treated as the return value.

Since Kotlin supports type push to, this can be simplified into two formats:

 val sum= { x: Int, y: Int -> x + y }
Copy the code
 val sum: (Int.Int) - >Int = { x, y -> x + y }
Copy the code

It is also supported in Kotlin that if the last argument to a function is a function, the Lambda expression passed as the corresponding argument can be placed outside the parentheses:

 // For example, testHighFun, we can place lambda outside the parentheses
 testHighFun(){
 //doSomeThing
 }
 ​
 // If the lambda expression is the only argument when called, the parentheses can be completely omitted
 testHighFun{
 //doSomeThing
 }
 ​
 // It is common for a lambda expression to have only one argument.
 // If the compiler can identify the signature itself, it may not declare a unique parameter and ignore ->. This parameter is implicitly declared as it: as follows
 testHighFun{
     //doSomeThing
     it.toString(it)
 }
Copy the code

Returns a value from a lambda expression

We can use qualified return syntax to explicitly return a value from lambda. Otherwise, the value of the last expression is implicitly returned. Refer to the official website for examples below

 ints.filter {
     val shouldFilter = it > 0 
     shouldFilter
 }
 ​
 ints.filter {
     val shouldFilter = it > 0 
     return@filter shouldFilter
 }
Copy the code

Ok, so that’s the basic use of Lambda.

So with all this talk, we’ve just talked about how Lambda works, so what’s the nature of Lambda? In fact, if you think about testHighFun above you can pass in a Lambda expression to get a sense that Lambda is essentially a function type object. This can also be seen by sending compiled Java code.

Anonymous functions and Lambda expressions:

  1. Both can be passed as arguments to higher-order functions.
  2. Both are function type objects by nature.

Remarks: the above is my personal understanding of higher-order functions, anonymous functions, Lambda expressions, what is wrong, but also ask you to correct.

Reference article:

  1. Juejin. Cn/post / 684490…
  2. Kotlin core programming
  3. Kotlin website