Kotlin. Anonymous functions (lambda). Features of use

Anonymous functions (lambda). Features of use. Examples


Contents


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