Анонимные функции (лямбда). Особенности использования. Примеры
Содержание
- 1. Неявный возврат в лямбда-выражениях
- 2. Передача аргументов в анонимную функцию
- 3. Замена аргумента. Ключевое слово it
- 4. Объявление анонимной функции без явно заданного типа
- 5. Передача лямбды (анонимной функции) в качестве параметра другой функции
- 6. Передача лямбды в функцию по сокращенному синтаксису
- 7. Встраивание. Ключевое слово inline
- 8. Ссылка на функцию. Передача ссылки на функцию в другую функцию. Оператор ::
- 9. Возврат функции из другой функции
- Связанные темы
Поиск на других ресурсах:
1. Неявный возврат в лямбда-выражениях
В анонимной функции (лямбда-выражении) использование ключевого слова return для возврата данных необязательно (в отличие от именуемых функций). Автоматически (неявно) возвращается результат последней инструкции. Например, если последней инструкцией была строка, то результатом возврата лямбда-выражения (анонимной функции) будет тип String.
Такой синтаксис является необходимостью, поскольку применение ключевого слова return вызовет неоднозначность. Непонятно, возвращать ли результат из функции, вызвавшей лямбда-выражение, или возвращать результат из лямбда-выражения.
Пример.
fun main(args: Array<String>) { // 1. Неявно возвращается тип String // 1.1. Объявить анонимную функцию val FuncHello: () -> String = { "Hello, world!" } // 1.2. Вывести результат вызова анонимной функции val res1 = FuncHello() println(res1) // Hello, world! // 2. Неявно возвращается тип Int // 2.1. Объявить анонимную функцию, принимающую один параметр типа Int // и возвращает тип Int неявно val Mult2: (Int) -> Int = { a -> a * 2 // неявный возврат типа Int, слово return отсутствует } // 2.2. Вызвать функцию Mult2 для того, чтобы умножить число на 2 val res2:Int res2 = Mult2(100) println(res2) // 200 }
Результат
Hello, world! 200
⇑
2. Передача аргументов в анонимную функцию
Анонимная функция может принимать любое количество аргументов. Для того чтобы анонимная функция принимала аргументы нужно описать типы этих аргументов. В наиболее общем случае вид анонимной функции, принимающей N аргументов выглядит примерно так
val FuncName: (paramType1, paramType2, ..., paramTypeN) -> returnType {
param1, param2, ..., paramN -> expression
}
здесь
- paramType1, paramType2, paramTypeN – типы параметров, получаемых функцией;
- param1, param2, paramN – имена параметров, получаемых функцией. Параметр param1 имеет тип paramType1, параметр param2 имеет тип paramType2 и так далее;
- expression – одно или несколько выражений.
Пример.
fun main(args: Array<String>) { // 1. Анонимная функция, получающая радиус круга и возвращающая площадь круга // 1.1. Объявление функции val Area: (Double) -> Double = { r -> Math.PI * r * r } // 1.2. Вызов функции println("Area(2.0) = " + Area(2.0)) // 2. Анонимная функция, получающая 3 параметра типа Float // и возвращающая их среднее арифметическое // 2.1. Объявление функции val Avg3: (Float, Float, Float) -> Float = { a, b, c -> (a + b + c) / 3 } // 2.2. Вызов функции println("Avg(7, 2, 8) = " + Avg3(7F, 2F, 8F)) }
Результат
Area(2.0) = 12.566370614359172 Avg(7, 2, 8) = 5.6666665
⇑
3. Замена аргумента. Ключевое слово it
Если анонимная функция принимает только один аргумент, то имя этого аргумента в теле функции может быть заменено ключевым словом it. Таким образом, есть два альтернативных случая обращения к аргументу:
- непосредственно по имени аргумента;
- с использованием ключевого слова it.
Ключевое слово it в теле анонимной функции реализовано со спецификатором val. А это значит, что использовать ключевое слово it в левой части оператора присваивания запрещено
it = value // ошибка, запрещено
Важно заметить, что ключевое слово it в теле анонимной функции описывает данные недостаточно ясно. Если анонимная функция имеет более сложный код, лучше использовать непосредственно имена аргументов. Ключевое слово it целесообразно использовать в случае маленьких лямбда-выражений.
Пример.
fun main(args: Array<String>) { // 1. Анонимная функция, получающая один аргумент – радиус круга // и возвращает длину круга // 1.1. Объявление функции без использования ключевого слова it val Circumference1: (Double)->Double = { r -> 2 * Math.PI * r // аргумент имеет имя r } // 1.2. Объявление функции с использованием it, // здесь имя аргумента r не указывается val Circumference2: (Double) -> Double = { 2 * Math.PI * it // используется it } // 1.3. Вызов функций println("Circumference1(5.0) = " + Circumference1(5.0)) println("Circumference2(5.0) = " + Circumference2(5.0)) // 2. Анонимная функция, получающая 1 параметр – число типа Int // и возвращает сумму цифр этого числа // 2.1. Объявление функции val SumNumbers: (Int) -> Int = { var sum = 0 var t = it // используется it вместо имени аргумента while (t>0) { sum = sum + t%10 t = t/10 } sum } // 2.2. Вызов функции val summ = SumNumbers(982) // 2.3. Вывод результата println("summ = " + summ) // summ = 19 }
Результат
Circumference1(5.0) = 31.41592653589793 Circumference2(5.0) = 31.41592653589793 summ = 19
⇑
4. Объявление анонимной функции без явно заданного типа
Анонимная функция может быть объявлена без задания типа возврата. В этом случае тип определяется автоматически. Такая особенность анонимных функций делает код более лаконичным.
В наиболее общем случае объявление анонимной функции с автоматическим определением типа имеет вид.
val FuncName = { arg1:typeArg1, arg2:typeArg2, ..., argN:typeArgN -> // Инструкции // ... }
здесь
- FuncName – имя функции;
- arg1, arg2, argN – имена аргументов, которые получает функция;
- typeArg1, typeArg2, typeArgN – соответственно типы аргументов arg1, arg2, argN.
Если анонимная функция не получает параметров, то вид функции примерно следующий
val FuncName = { // Інструкції // ... }
Пример.
fun main(args: Array<String>) { // 1. Анонимная функция, получающая 3 числа типа Int // и находящая максимальное между ними // 1.1. Объявление функции, тип возврата определяется автоматически val Max3 = { num1: Int, num2: Int, num3: Int -> var max = num1 if (max < num2) max = num2 if (max < num3) max = num3 max } // 1.2. Вызов функции val res = Max3(5, 9, 7) println("Max3(5, 9, 7) = " + res) // ------------------------------------------------ // 2. Анонимная функция, получающая строку и символ и возвращающая // количество вхождений символа в строке // 2.1. Объявить функцию val GetCharOccurences = { s: String, c: Char -> var count: Int = 0 for (t in s) if (t == c) count++ count } // 2.2. Вызов функции val nOccur = GetCharOccurences("abc abd def", 'a') println("nOccur = " + nOccur) // ---------------------------------------------------- // 3. Анонимная функция, не получающая параметров // 3.1. Объявление val PrintHello = { println("Hello, world!") } // 3.2. Вызов PrintHello() }
Результат
Max3(5, 9, 7) = 9 nOccur = 2 Hello, world!
⇑
5. Передача лямбды (анонимной функции) в качестве параметра другой функции
В любую функцию можно передавать произвольное количество аргументов любого типа. Помимо данных разных типов этими аргументами могут быть лямбды или анонимные функции.
Процесс передачи лямбды в другую функцию может выглядеть следующим образом. В теле некоторой функции Fun1():
- объявляется анонимная функция (лямбда), передаваемая в другую функцию Fun2();
- реализуется вызов функции Fun2() с передачей объявленной лямбды.
Вид функции Fun1() приблизительно следующий
fun Fun1(...) { ... // Объявление анонимной функции (лямбды) val Lambda = { params_Lambda -> // Инструкции в теле лямбды ... } ... // Вызов функции Fun2() с передачей лямбды как аргумента Fun2(args, Lambda); // передается имя лямбды }
здесь
- Fun1 – имя некоторой функции в которой объявляется лямбда;
- args – некоторые дополнительные аргументы, передаваемые в функцию Fun2();
- Lambda – имя анонимной функции;
- params_Lambda – параметры, получаемые лямбдой.
В свою очередь, функция Fun2():
- принимает в качестве входного параметра спецификацию лямбды без реализации;
- использует вызов лямбды в коде.
Приблизительный вид функции Fun2() следующий:
fun Fun2(parameters, Lambda(params_Lambda)->returnLambda) { ... // Использование лямбды в теле функции Lambda(args_Lambda) ... }
здесь
- parameters – дополнительные параметры, получаемые функцией Fun2() кроме лямбды;
- params_Lambda – список параметров лямбды, разделенных запятой;
- return_Lambda – тип данных, возвращаемый лямбдой.
Пример.
В примере формируется экземпляр лямбды D(), который на основе коэффициентов a, b, c вычисляет дискриминант. Затем эта лямбда передается в функцию Equation(), вызывающую ее код.
fun main(args: Array<String>) { // Передача в анонимную функцию другой функции. // Решение квадратного уравнения // 1. Объявить анонимную функцию, вычисляющую дискриминант val D = { a: Double, b: Double, c: Double -> b * b - 4 * a * c } // 2. Вызвать функцию Equation(), которой передается имя лямбды D Equation(3.0, -3.0, -2.0, D) } // Функция, которая находит решение квадратного уравнения и выводит его на экран, // функция получает параметром анонимную функцию, вычисляющую дискриминант 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) // вызов лямбды D(a,b,c) val x2: Double = (-b + Math.sqrt(D(a, b, c))) / (2 * a) // вызов лямбды D(a,b,c) println("x1 = " + x1) println("x2 = " + x2) } }
Результат
x1 = -0.4574271077563381 x2 = 1.457427107756338
⇑
6. Передача лямбды в функцию по сокращенному синтаксису
Лямбда может передаваться в функцию по упрощенному синтаксису без указания круглых скобок (). В таком случае способы использования лямбды расширяются.
Использование лямбды с круглыми скобками выглядит примерно так
Fn({ instructions })
здесь
- Fn – имя функции, в которую передается лямбда;
- instructions – некоторые операции, используемые в лямбде.
Использование лямбды без круглых скобок выглядит проще
Fn { instructions }
Сокращенный синтаксис можно использовать в тех случаях, когда лямбда передается в функцию последним аргументом.
Пример.
fun main(args: Array<String>) { // Упрощенный синтаксис передачи лямбды // Демонстрируется поиск числа 5 в массиве целых чисел // 1. Создать массив из 10 целых чисел var AI : Array<Int> = arrayOf( 7, 3, 7, 9, 5, 5, 6, 4, 7, 2 ) // 2. Вычислить количество чисел 5 с использованием скобок (), // использовать стандартную функцию count() val num5 = AI.count({ it == 5 }) // используются скобки () println("num5 = " + num5) // 3. Вычислить количество чисел 7 с помощью упрощенной записи val num7 = AI.count {it == 7} // скобки () отсутствуют println("num7 = " + num7) }
Результат
num5 = 2 num7 = 3
⇑
7. Встраивание. Ключевое слово inline
Использование анонимных функций (лямбд) приводит к неэффективным затратам памяти. Это связано с тем, что на каждую лямбду создается соответствующий экземпляр объекта. Этот экземпляр содержит данные (переменные), для которых выделяется память. При передаче лямбды в функцию в качестве параметра этих данных создается еще одна копия в памяти, то есть, создается еще один экземпляр объекта.
Чтобы избежать копии лямбды, в языке Kotlin предусмотрено использование так называемого встраивания (инлайнинга). Встраивание обеспечивается с использованием ключевого слова inline перед объявлением функции. Приблизительный вид такого объявления следующий
inline fun Func(parameters, Lambda(params_Lambda)->return_Lambda) {
...
}
Встраивание в программах может применяться в любых функциях, за исключением рекурсивных функций. Рекурсивная функция – это функция, которая в своем коде вызывает саму себя.
Пример.
Демонстрируется программа вычисления площади треугольника по заданным сторонам. В программе объявляется лямбда, вычисляющая полупериметр. Затем лямбда передается в функцию AreaTriangle(), которая вычисляет площадь треугольника по трем сторонам. Код функции AreaTriangle() оптимизируется благодаря использованию ключевого слова inline.
fun main(args: Array<String>) { // Встраивание. Ключевое слово inline // Вычисление площади треугольника. // 1. Объявить лямбду, которая вычисляет полупериметр по трем сторонам val P: (Double, Double, Double) -> Double = { a, b, c -> (a + b + c) / 2 } // 2. Вызвать вычисление площади треугольника со сторонами 5, 6, 7 val area = AreaTriangle(5.0, 6.0, 7.0, P) // 3. Вывести результат print("AreaTriangle(5,6,7) = " + area) } // inline-функция, вычисляющая площадь по формуле Герона inline fun AreaTriangle(a:Double, b:Double, c:Double, P:(Double,Double,Double)->Double) : Double { // 1. Проверка, можно ли из сторон a, b, c создать треугольник if (((a+b)<c)||((b+c)<a)||((a+c)<b)) return 0.0 // 2. Вычислить площадь, при вычислении используется анонимная функция 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 area }
Результат
AreaTriangle(5,6,7) = 14.696938456699069
⇑
8. Ссылка на функцию. Передача ссылки на функцию в другую функцию. Оператор ::
Если объявляется функция, можно сформировать ссылку на эту функцию. Ссылка на функцию объявляется с помощью оператора :: по следующему синтаксису
::FuncName
здесь
- FuncName – имя функции, которая была ранее объявлена с использованием ключевого слова fun или это анонимная функция (лямбда).
Ссылки на функцию можно использовать где угодно, где допускается лямбда-выражение. С помощью ссылок на функцию можно организовывать своего рода диспетчеризацию выполненной работы в программе.
Пример.
В примере объявляются 3 функции Length(), Area(), Volume(), которые вычисляют соответственно длину окружности, площадь круга и объем шара. Все 3 функции получают 1 параметр – радиус.
В функцию Calc() передается ссылка на функцию, получающую параметр типа Double. Из функции Calc() происходит вызов другой функции, передаваемой ему как параметр.
Из функции main() происходит вызов функции Calc(). В функцию Calc() поочередно передаются ссылки на функции Length(), Area(), Volume() для получения результата.
fun main(args: Array<String>) { // Ссылка на функцию // 1. Получить радиус var radius : Double print("radius = ") radius = readLine().toString().toDouble() // 2. Вычислить длину окружности с помощью функции Calc() и вывести ее, // в функцию передается функция Length() по синтаксису ::Length Calc("Length(" + radius + ") = ", radius, ::Length) // 3. Вычислить площадь круга и вывести ее, // в функцию Calc() передается функция Length() Calc("Area(" + radius + ") = ", radius, ::Area) // 4. Вычислить объем шара и вывести его функцией Calc() val v = ::Volume // ::Volume - получить ссылку на функцию Calc("Volume(" + radius + ") = ", radius, v) // передается ::Volume } // Функция, получающая ссылку на другую функцию в качестве параметра, // функция вызывает входящую функцию fun Calc(message:String, radius:Double, Func: (Double) -> Double) { // Вывести на экран результат вызова функции Fun() println(message + Func(radius)) } // Функция, которая по заданному радиусу вычисляет длину окружности fun Length(radius:Double) = 2.0*Math.PI*radius // Функция, которая по заданному радиусу вычисляет площадь окружности fun Area(radius:Double) = Math.PI*radius*radius // Функция, вычисляющая объем шара fun Volume(radius:Double) = 4.0/3.0*Math.PI*radius*radius*radius
Результат
radius = 3 Length(3.0) = 18.84955592153876 Area(3.0) = 28.274333882308138 Volume(3.0) = 113.09733552923255
⇑
9. Возврат функции из другой функции
Функция может возвращать другую функцию. Возврат функции происходит оператором return. Возвращаться может как анонимная функция, так и ссылка на именуемую функцию.
В случае возвращения анонимной функции код возврата примерно такой
fun Func(parameters) { ... return { // Инструкции в теле анонимной функции // ... } }
здесь
- Func – имя функции, из которой возвращается анонимная функция;
- parameters – параметры функции.
Если возвращается именуемая функция, реализуемая в программе с использованием ключевого слова fun, то код возврата примерно следующий
fun Func(parameters) { ... return ::NamedFunc // вернуть именованную функцию } fun NamedFunc(parameters2) { ... }
Пример 1.
В примере демонстрируется возврат функцией Calc() одной из трех анонимных функций, вычисляющих длину окружности, площадь круга и объем шара. В функции main() вызывается функция Calc() для разных случаев.
fun main(args: Array<String>) { // Вызвать функцию Calc var fn1 = Calc(1) // получить функцию вычисления длины окружности val length = fn1(2.0) // вычислить длину окружности радиуса 2.0 println("length = " + length) var fn2 = Calc(2) // получить функцию вычисления площади окружности val area = fn2(2.0) // вычислить площадь окружности радиуса 2.0 println("area = " + area) // Вычислить объем шара радиуса 3.0 val volume = Calc(3)(3.0) println("volume = " + volume) } // Функция, получающая целое число и на основе этого числа возвращающая соответствующую функцию fun Calc(numFunc:Int): (Double)->Double { // Вернуть функцию вычисления длины окружности if (numFunc==1) return { radius -> 2*Math.PI*radius } // Вернуть функцию вычисления площади круга if (numFunc==2) return { radius -> Math.PI*radius*radius } // Вернуть функцию вычисления объема шара return { radius -> 4.0/3*Math.PI*radius*radius*radius } }
Результат
length = 12.566370614359172 area = 12.566370614359172 volume = 113.09733552923255
Пример 2.
Решение предыдущего примера другим способом. Здесь функция Calc() возвращает ссылку на именуемые функции Length(), Area(), Volume().
fun main(args: Array<String>) { // Вызвать функцию Calc() для разных случаев var fn1 = Calc(1) // получить функцию вычисления длины окружности val length = fn1(1.0) // вычислить длину окружности радиуса 1.0 println("length = " + length) var fn2 = Calc(2) // получить функцию вычисления площади круга val area = fn2(1.0) // вычислить площадь круга радиуса 1.0 println("area = " + area) // вычислить объем шара радиуса 3.0 val volume = Calc(3)(1.0) println("volume = " + volume) } // Функция, получающая целое число и на основе этого числа возвращающая соответствующую функцию fun Calc(numFunc:Int): (Double)->Double { // Если параметр равен 1, // то вернуть ссылку на функцию Length if (numFunc==1) return ::Length // Если параметр равен 2, // то вернуть ссылку на функцию Area if (numFunc==2) return ::Area // Иначе вернуть ссылку на функцию Volume return ::Volume } // Функция, которая по заданному радиусу вычисляет длину окружности fun Length(radius:Double) = 2.0*Math.PI*radius // Функция, которая по заданному радиусу вычисляет площадь круга fun Area(radius:Double) = Math.PI*radius*radius // Функция, вычисляющая объем шара fun Volume(radius:Double) = 4.0/3.0*Math.PI*radius*radius*radius
Результат
length = 6.283185307179586 area = 3.141592653589793 volume = 4.1887902047863905
⇑
Связанные темы
- Анонимные функции. Лямбда-выражения. Функциональный тип
- Функции. Ключевое слово fun. Рефакторинг функции. Оператор return
⇑