Lambda expressions and Kotlin higher-order functions

concept

Lambda expressions, or lambda for short, are essentially small pieces of code that can be passed to other functions. You can easily extract generic code structures into library functions. A higher-order function is one that takes another function as its argument or return value. In Kotlin, functions can be represented as lambda or function references. Therefore, any function that takes a lambda or function reference as an argument or return value is a higher-order function.

lambda

One of the new features of Java 8 is the introduction of lambda expressions. Functional programming offers a particular approach to the problem: treating functions as values. You can pass functions directly, without having to declare a class and then pass an instance of that class (a scenario often seen in Java anonymous inner class implementation interface callbacks). Efficient and direct passing of code blocks makes code more concise. Let’s look at an example that defines a button click event. Implemented using anonymous inner classes:

button.setOnClickListener(new OnCLickListener(){
    @Override
    public void onCLick(View view){
        doSomething(); }});Copy the code

In Kotlin and Java 8, lambda can be used:

button.setOnClickListener{           
    doSomething()
}
Copy the code

These two pieces of code do exactly the same thing, but the latter is cleaner and easier to read.

Syntax for lambda expressions

A lambda encodes a short behavior that can be passed as a value. Can be declared independently and stored in a variable. The syntax for declaring a lambda expression is as follows:

{x:Int,y:Int -> x+y}
Copy the code

Kotlin’s lambda expressions are always surrounded by curly braces, with arrows separating the argument list from the body of the lambda function. Before the arrow is the parameter, after the arrow is the function body. Let’s look at the use of lambda.

Use of lambda expressions

Suppose we have a higher-order function that takes two ints, returns a function of type Int, and returns an Int as the result of some operations on 2 and 3. The code looks like this:

private fun lambdaFunction  (function:(Int, Int)->Int):Int{
        return function(2,3)
    }
Copy the code

If we want to calculate the sum of 2 and 3, we can call:

lambdaFunction({x:Int,y:Int -> x+y})
Copy the code

But this code is somewhat verbose, with excessive symbols ruining the readability of the code. Fortunately, as with local variables, if the type of a lambda argument can be derived without specifying it explicitly, the code can be reduced to:

lambdaFunction({x,y -> x+y})
Copy the code

Second, if the lambda expression is the last argument to a function call, it can be placed outside curly braces to continue improving the code:

lambdaFunction(){x,y -> x+y}
Copy the code

Finally, when lambda is the only argument to the function, you can also remove the empty parenthesis pairs from the code, so the resulting condensed code is:

lambdaFunction{x,y -> x+y}
Copy the code

All four syntactic forms have the same meaning, but the last is more readable. Also, if the current context expects a lambda with only one argument and the type of the argument can be deduced, the default parameter name it can be used to refer to named arguments. For example,

user.let{
    it.age
}
Copy the code

Where it refers to the input View, note that the IT convention can greatly shorten the code, but should not be abused. Next, let’s talk about the concept that goes hand in hand with lambda: capturing variables from context

Access variables in scope

First, let’s look at some Java code:

private void fun(User me) {
        int a = 100;
        new Runnable() {
            @Override
            public void run() { me.setAge(a); }}; }Copy the code

When declaring an anonymous inner class within a function, you can refer to the function’s arguments and local variables within the anonymous inner class. (Note that before JDK8, if we needed to access a local variable in an anonymous inner class, the local variable had to be modified with a final modifier.) Lambda expressions can do the same thing. We use forEach to show this behavior:

private fun lambdaFunction  (list: List<User>,age: Int){
        list.forEach { 
            it.age = age
        }
    }
Copy the code

This behavior of accessing external variables from within lambda is said to be captured by lambda. Now that lambda expressions are basically covered, let’s move on to higher-order functions in Kotlin:

Higher-order functions

Recall the higher-order function definition from the beginning:

A higher-order function is one that takes another function as its argument or return value. In Kotlin, functions can be represented as lambda or function references. Therefore, any function that takes a lambda or function reference as an argument or return value is a higher-order function

As can be seen from the definition, lambdaFunction used in the previous paper is a higher-order function. Before you can declare a higher-order function, you must first know what a function type is

Function types

Review the lambdaFunction function:

private fun lambdaFunction  (function:(Int, Int)->Int):Int{
        return function(2,3)
    }
Copy the code

Where (Int, Int)->Int declares a function type. Function types include parameter types and return types. Use the -> arrow to separate parameter types from return types. Note that the Unit type is used to express that a function does not return any useful value. It can be omitted when declaring a normal function. However, an explicit return value is always required during the declaration of a function type, so Unit cannot be omitted in this scenario

(Int, Int)->Unit
Copy the code

At the same time, the types of arguments in lambda expressions {x,y -> x+y} are omitted because their types are already specified in the parameter types of function types and do not need to be restated in the lambda definition itself. We can specify names for arguments in a function type declaration: (a:Int, b:Int)->Int, and the corresponding lambda expression can be:

{x,y -> x+y} {a,b-> a+b}Copy the code

Parameter names do not affect type matching. When declaring a lambda, you can use any name that conforms to the rule, but naming improves the readability of the code.

A function whose input parameter is a function

Once you know how to declare a higher-order function, the next step is to implement it. Continuing with lambdaFunction as an example, its code is as follows:

private fun lambdaFunction  (function:(Int, Int)->Int):Int{
        return function(2,3)
}
Copy the code

Use method:

LambdaFunction {x,y -> x*y} {x,y -> x+y}Copy the code

Calling functions as parameters is the same as calling ordinary functions, such as function in lambdaFunction. The principle is as follows:

Function types are declared as ordinary interfaces: a function type variable is an implementation of the FunctionN interface, and each interface defines an invoke method that executes the function if it is not called. A variable of function type is an instance of the implementation class that implements the corresponding FunctionN interface, and the invoke method of the implementation class contains the body of a lambda function.

A function that returns a function

Returning a function from a function is not as useful as passing the function as an argument. But when a piece of logic in a program changes because of other conditions, that’s where it comes in. Again, take multiplication and addition:

private fun lambdaFunction  (x:Int):(a:Int,b: Int)->Int{
        if (x>0){
            return {a, b -> a*b}
        } else{
            return{a, b -> a+b}}} lambdaFunction(2)(1,2) val lam = lambdaFunction(1,2)Copy the code

The function lambdaFunction returns summation and quadrature functions based on the value of the input parameter A. A function that returns a function (a:Int,b: Int)->Int;

conclusion

At this point, you should have a clear understanding of Kotlin’s higher-order functions and lambda expressions. However, this article is only a primer on the use of higher-order functions and lambda. It does not explain how lambda expressions are compiled into anonymous classes and the resulting inline functions, nor does it cover the control flow and set function apis in higher-order functions. For more information, stay tuned to the author’s blog.