Anonymous functions (lambda). Features of use. Examples
Contents
- 1. Implicit return in lambda expressions
- 2. Passing arguments to an anonymous function
- 3. Replacing an argument. Keyword it
- 4. Declaring an anonymous function without an explicit type
- 5. Passing a lambda (anonymous function) as a parameter to another function
- 6. Passing a lambda to a function using shortened syntax
- 7. Inlining. The inline keyword
- 8. Function reference. Passing a function reference to another function. Operator ::
- 9. Returning a function from another function
- Related topics
Search other resources:
1. Implicit return in lambda expressions
In an anonymous function (lambda expression), using the return keyword to return data is optional (unlike named functions). Automatically (implicitly) the result of the last instruction is returned. For example, if the last instruction was a string, then the lambda expression (anonymous function) will return a String.
This syntax is necessary because using the return keyword will cause ambiguity. It is not clear whether to return the result from the function that called the lambda expression, or to return the result from the lambda expression.
Example.
fun main(args: Array<String>) { // 1. Implicitly returning String type // 1.1. Declare an anonymous function val FuncHello: () -> String = { "Hello, world!" } // 1.2. Print the result of an anonymous function call val res1 = FuncHello() println(res1) // Hello, world! // 2. Implicitly returning type Int // 2.1. Declare an anonymous function that takes one parameter of type Int // and returns type Int implicitly val Mult2: (Int) -> Int = { a -> a * 2 // implicit return of type Int, the word return is missing } // 2.2. Call the Mult2 function to multiply a number by 2 val res2:Int res2 = Mult2(100) println(res2) // 200 }
Result
Hello, world! 200
⇑
2. Passing arguments to an anonymous function
An anonymous function can take any number of arguments. In order for an anonymous function to accept arguments, you need to describe the types of these arguments. In the most general case, an anonymous function that takes N arguments looks like this
val FuncName: (paramType1, paramType2, ..., paramTypeN) -> returnType {
param1, param2, ..., paramN -> expression
}
here
- paramType1, paramType2, paramTypeN – types of parameters received by the function;
- param1, param2, paramN – the names of the parameters received by the function. param1 is of type paramType1, param2 is of type paramType2, and so on;
- expression – one or more expressions.
Example.
fun main(args: Array<String>) { // 1. Anonymous function that takes the radius of a circle and returns the area of the circle // 1.1. Function declaration val Area: (Double) -> Double = { r -> Math.PI * r * r } // 1.2. Function call println("Area(2.0) = " + Area(2.0)) // 2. An anonymous function that takes 3 parameters of type Float // and returns their arithmetic mean // 2.1. Function declaration val Avg3: (Float, Float, Float) -> Float = { a, b, c -> (a + b + c) / 3 } // 2.2. Function call println("Avg(7, 2, 8) = " + Avg3(7F, 2F, 8F)) }
Result
Area(2.0) = 12.566370614359172 Avg(7, 2, 8) = 5.6666665
⇑
3. Replacing an argument. Keyword it
If an anonymous function takes only one argument, then the name of that argument in the function body can be replaced by the it keyword. Thus, there are two alternative cases for accessing an argument:
- directly by the argument name;
- using the it keyword.
The keyword it in the body of the anonymous function is implemented with the qualifier val. This means that it is forbidden to use the it keyword on the left side of an assignment statement.
it = value // error, forbidden
It is important to note that the it keyword in the body of the anonymous function does not describe the data clearly enough. If the anonymous function has more complex code, it’s better to use the argument names directly. The it keyword is useful for small lambda expressions.
Example.
fun main(args: Array<String>) { // 1. An anonymous function that takes one argument - the radius of the circle // and returns the length of the circle // 1.1. Declaring a function without using the it keyword val Circumference1: (Double)->Double = { r -> 2 * Math.PI * r // the name of argument is r } // 1.2. Declaring a function using it, // argument name r is not specified here val Circumference2: (Double) -> Double = { 2 * Math.PI * it } // 1.3. Function call println("Circumference1(5.0) = " + Circumference1(5.0)) println("Circumference2(5.0) = " + Circumference2(5.0)) // 2. Anonymous function that takes 1 parameter - a number of type Int // and returns the sum of the digits of this number // 2.1. Function declaration val SumNumbers: (Int) -> Int = { var sum = 0 var t = it // use it instead of argument name while (t>0) { sum = sum + t%10 t = t/10 } sum } // 2.2. Call the function val summ = SumNumbers(982) // 2.3. Print the result println("summ = " + summ) // summ = 19 }
Result
Circumference1(5.0) = 31.41592653589793 Circumference2(5.0) = 31.41592653589793 summ = 19
⇑
4. Declaring an anonymous function without an explicit type
An anonymous function can be declared without specifying a return type. In this case, the type is determined automatically. This feature of anonymous functions makes the code more concise.
In the most general case, the declaration of an anonymous function with automatic type detection has the form.
val FuncName = { arg1:typeArg1, arg2:typeArg2, ..., argN:typeArgN -> // Instructions // ... }
here
- FuncName—function name;
- arg1, arg2, argN are the names of the arguments that the function receives;
- typeArg1, typeArg2, typeArgN – types of arguments arg1, arg2, argN respectively.
If the anonymous function does not receive parameters, then the form of the function is something like this
val FuncName = { // Інструкції // ... }
Example.
fun main(args: Array<String>) { // 1. Anonymous function that takes 3 Int numbers // and finding the maximum between them // 1.1. Function declaration, return type determined automatically val Max3 = { num1: Int, num2: Int, num3: Int -> var max = num1 if (max < num2) max = num2 if (max < num3) max = num3 max } // 1.2. Function call val res = Max3(5, 9, 7) println("Max3(5, 9, 7) = " + res) // ------------------------------------------------ // 2. An anonymous function that takes a string and a character and returns // number of occurrences of a character in a string // 2.1. Declare function val GetCharOccurences = { s: String, c: Char -> var count: Int = 0 for (t in s) if (t == c) count++ count } // 2.2. Call the function val nOccur = GetCharOccurences("abc abd def", 'a') println("nOccur = " + nOccur) // ---------------------------------------------------- // 3. Anonymous function that does not take parameters // 3.1. Declaration val PrintHello = { println("Hello, world!") } // 3.2. Invoke PrintHello() }
Result
Max3(5, 9, 7) = 9 nOccur = 2 Hello, world!
⇑
5. Passing a lambda (anonymous function) as a parameter to another function
You can pass any number of arguments of any type to any function. In addition to data of different types, these arguments can be lambdas or anonymous functions.
The process of passing a lambda to another function might look like this. In the body of some Fun1() function:
- an anonymous function (lambda) is declared, passed to another function Fun2();
- the Fun2() function call is implemented with the declared lambda passed.
The form of the Fun1() function is approximately the following
fun Fun1(...) { ... // Declaration of an anonymous function (lambda) val Lambda = { params_Lambda -> // Instructions in the lambda body ... } ... // Calling the Fun2() function with a lambda as an argument Fun2(args, Lambda); // the name of the lambda is passed }
here
- Fun1 – the name of some function in which the lambda is declared;
- args – some additional arguments passed to the Fun2() function;
- Lambda – the name of the anonymous function;
- params_Lambda – parameters received by the lambda.
In turn, the Fun2() function:
- takes as an input parameter a lambda specification without an implementation;
- uses a lambda call in the code.
An approximate view of the Fun2() function is as follows:
fun Fun2(parameters, Lambda(params_Lambda)->returnLambda) { ... // Using a lambda in a function body Lambda(args_Lambda) ... }
here
- parameters – additional parameters received by the Fun2() function, except for the lambda;
- params_Lambda – comma-separated list of lambda parameters;
- return_Lambda is the data type returned by the lambda.
Example.
In the example, an instance of the lambda D() is formed, which, based on the coefficients a, b, c, calculates the discriminant.
fun main(args: Array<String>) { // Passing to an anonymous function of another function. // Solving a quadratic equation // 1. Declare an anonymous function that calculates the discriminant val D = { a: Double, b: Double, c: Double -> b * b - 4 * a * c } // 2. Call the Equation() function, which is passed the name of the lambda D Equation(3.0, -3.0, -2.0, D) } // A function that finds a solution to a quadratic equation and displays it on the screen, // function takes as a parameter an anonymous function that calculates the discriminant fun Equation(a:Double, b:Double, c:Double, D:(Double, Double, Double)->Double) { if (D(a,b,c)<0) println("Solution has no roots.") else { val x1: Double = (-b - Math.sqrt(D(a, b, c))) / (2 * a) // lambda call D(a,b,c) val x2: Double = (-b + Math.sqrt(D(a, b, c))) / (2 * a) // lambda call D(a,b,c) println("x1 = " + x1) println("x2 = " + x2) } }
Result
x1 = -0.4574271077563381 x2 = 1.457427107756338
⇑
6. Passing a lambda to a function using shortened syntax
A lambda can be passed to a function using a simplified syntax without parentheses (). In such a case, the ways in which lambdas can be used are expanded.
Using a lambda with parentheses looks something like this
Fn({ instructions })
here
- Fn – name of the function to which the lambda is passed;
- instructions – some of the operations used in the lambda.
Using a lambda without parentheses looks easier
Fn { instructions }
Shorthand syntax can be used when the lambda is passed to the function as the last argument.
Example.
fun main(args: Array<String>) { // Simplified syntax for passing lambda // Demonstrates searching for the number 5 in an array of integers // 1. Create an array of 10 integers var AI : Array<Int> = arrayOf( 7, 3, 7, 9, 5, 5, 6, 4, 7, 2 ) // 2. Calculate the number of numbers 5 using brackets (), // use the standard count() function val num5 = AI.count({it==5}) // parentheses are used () println("num5 = " + num5) // 3. Calculate the number of numbers 7 using simplified notation val num7 = AI.count {it == 7} // parentheses () are missing println("num7 = " + num7) }
Result
num5 = 2 num7 = 3
⇑
7. Inlining. The inline keyword
The use of anonymous functions (lambdas) results in inefficient memory usage. This is due to the fact that for each lambda a corresponding object instance is created. This instance contains data (variables) for which memory is allocated. When passing a lambda to a function as a parameter of this data, another copy is created in memory, that is, another instance of the object is created.
To avoid lambda copy, the Kotlin language provides for the use of the so-called inlining. Inlining is provided by using the inline keyword before the function declaration. An approximate form of such declaration is as follows
inline fun Func(parameters, Lambda(params_Lambda)->return_Lambda) {
...
}
Inlining in programs can be used in any function except recursive functions. A recursive function is a function that calls itself in its code.
Example.
Demonstrates a program for calculating the area of a triangle given sides. The program declares a lambda that calculates the semiperimeter. The lambda is then passed to the AreaTriangle() function, which calculates the area of the triangle given the three sides. The AreaTriangle() function code is optimized by using the inline keyword.
fun main(args: Array<String>) { // Inlining. The inline keyword // Calculating the area of a triangle. // 1. Declare a lambda that calculates the semi-perimeter given three sides val P: (Double, Double, Double) -> Double = { a, b, c -> (a + b + c) / 2 } // 2. Call the calculation of the area of a triangle with sides 5, 6, 7 val area = AreaTriangle(5.0,6.0,7.0, P) // 3. Display the result print("AreaTriangle(5,6,7) = " + area) } // inline-function that calculates the area using Heron's formula inline fun AreaTriangle(a:Double, b:Double, c:Double, P:(Double,Double,Double)->Double) : Double { // 1. Checking if it is possible to create a triangle from sides a, b, c if (((a+b)<c)||((b+c)<a)||((a+c)<b)) return 0.0 // 2. Calculate the area, the calculation uses the anonymous function P() val area = Math.sqrt(P(a,b,c)*(P(a,b,c)-a)*(P(a,b,c)-b)*(P(a,b,c)-c)) // 3. Return the result return area }
Result
AreaTriangle(5,6,7) = 14.696938456699069
⇑
8. Function reference. Passing a function reference to another function. Operator ::
If a function is declared, it is possible to form a reference to this function. A function reference is declared using the :: operator with the following syntax
::FuncName
here
- FuncName is the name of a function that was previously declared using the fun keyword or is an anonymous function (lambda).
Function references can be used anywhere a lambda expression is allowed. Using function references, you can organize a kind of dispatching of the work done in the program.
Example.
The example declares 3 functions Length(), Area(), Volume(), which calculate the length of a circle, the area of a circle, and the volume of a sphere, respectively. All 3 functions receive 1 parameter – the radius. A reference to a function that receives a Double type parameter is passed to the Calc() function. From the Calc() function, another function is called, passed to it as a parameter.
The main() function calls the Calc() function. References to the Length(), Area(), Volume() functions are passed to the Calc() function in turn to obtain the result.
fun main(args: Array<String>) { // Reference to function // 1. Get a radius var radius : Double print("radius = ") radius = readLine().toString().toDouble() // 2. Calculate the circumference of a circle using the Calc() function and display it, // the Length() function is passed to the function using the ::Length syntax Calc("Length(" + radius + ") = ", radius, ::Length) // 3. Calculate the area of a circle and output it, // the Length() function is passed to the Calc() function Calc("Area(" + radius + ") = ", radius, ::Area) // 4. Calculate the volume of a sphere and display it with the Calc() function val v = ::Volume // ::Volume - get a reference to a function Calc("Volume(" + radius + ") = ", radius, v) // ::Volume is passed } // A function that receives a reference to another function as a parameter, // the function calls the incoming function fun Calc(message:String, radius:Double, Func: (Double) -> Double) { // Display the result of calling the Fun() function println(message + Func(radius)) } // Function that calculates the circumference of a circle fun Length(radius:Double) = 2.0*Math.PI*radius // Function that calculates the area of a circle fun Area(radius:Double) = Math.PI*radius*radius // Function that calculates the volume of a sphere fun Volume(radius:Double) = 4.0/3.0*Math.PI*radius*radius*radius
Result
radius = 3 Length(3.0) = 18.84955592153876 Area(3.0) = 28.274333882308138 Volume(3.0) = 113.09733552923255
⇑
9. Returning a function from another function
A function can return another function. The function is returned by the return statement. Both an anonymous function and a reference to a named function can be returned.
In the case of returning an anonymous function, the return code is something like this
fun Func(parameters) { ... return { // Statements in the body of an anonymous function // ... } }
here
- Func – name of the function from which the anonymous function is returned;
- parameters – function parameters.
If a named function is returned that is implemented in the program using the fun keyword, then the return code is something like this
fun Func(parameters) { ... return ::NamedFunc // return named function } fun NamedFunc(parameters2) { ... }
Example 1.
The example demonstrates that the Calc() function returns one of three anonymous functions that calculate the circumference, the area of a circle, and the volume of a sphere.
In the main() function, the Calc() function is called for various cases.
fun main(args: Array<String>) { // Call the Calc() function var fn1 = Calc(1) // get a function to calculate the circumference of a circle val length = fn1(2.0) // calculate the circumference of a circle with radius 2.0 println("length = " + length) var fn2 = Calc(2) // get the function for calculating the area of a circle val area = fn2(2.0) // calculate the area of a circle with radius 2.0 println("area = " + area) // Calculate the volume of a sphere of radius 3.0 val volume = Calc(3)(3.0) println("volume = " + volume) } // A function that takes an integer and, based on this number, returns the corresponding function fun Calc(numFunc:Int): (Double)->Double { // Return the function for calculating the circumference if (numFunc==1) return { radius -> 2*Math.PI*radius } // Return the function for calculating the area of a circle if (numFunc==2) return { radius -> Math.PI*radius*radius } // Return the function for calculating the volume of a sphere return { radius -> 4.0/3*Math.PI*radius*radius*radius } }
Result
length = 12.566370614359172 area = 12.566370614359172 volume = 113.09733552923255
Example 2.
Solution of the previous example in a different way. Here the Calc() function returns a reference to the named functions Length(), Area(), Volume().
fun main(args: Array<String>) { // Call the Calc() function for different cases var fn1 = Calc(1) // get a function to calculate the circumference val length = fn1(1.0) // calculate the circumference of a circle with radius 1.0 println("length = " + length) var fn2 = Calc(2) // get the function of calculating the area of a circle val area = fn2(1.0) // calculate the area of a circle of radius 1.0 println("area = " + area) // calculate the volume of a sphere of radius 3.0 val volume = Calc(3)(1.0) println("volume = " + volume) } // A function that takes an integer and, based on this number, returns the corresponding function fun Calc(numFunc:Int): (Double)->Double { // If the parameter is 1, // then return a reference to the Length function if (numFunc==1) return ::Length // If the parameter is 2, // then return a reference to the Area function if (numFunc==2) return ::Area // Otherwise, return a reference to the Volume function return ::Volume } // A function that calculates the circumference fun Length(radius:Double) = 2.0*Math.PI*radius // A function that calculates the area of a circle fun Area(radius:Double) = Math.PI*radius*radius // A function that calculates the volume of a sphere fun Volume(radius:Double) = 4.0/3.0*Math.PI*radius*radius*radius
Result
length = 6.283185307179586 area = 3.141592653589793 volume = 4.1887902047863905
⇑
Related topics
- Anonymous functions. Lambda expressionss. Functional type. Examples
- Functions. The fun keyword. Refactoring a function. The return statement
⇑