Kotlin функции высшего порядка

Kotlin функции высшего порядка

    Статьи, 24 июля 2018 в 10:37

В этой статье мы рассмотрим самые распространенные способы «магического» использования функций в Kotlin.

Extension Functions

Начнем с простого: расширения классов без наследования. Мы можем, не изменяя класс String и все использующие его пакеты, расширить этот класс, добавив новый метод или свойство (поле). Пусть у нас есть метод deleteSpaces() :

И мы можем использовать этот метод так, как будто он является частью класса String . Пользователь увидит это так:

После компиляции же этот метод будет выглядеть примерно так (часть кода была упрощена, чтобы вам легче было понять суть):

Отсюда можно сделать вывод, что внутри метода deleteSpaces() у нас есть доступ только к публичным полям и методам класса, благодаря чему инкапсуляция не нарушается.

Кроме Extension Functions в Kotlin по аналогии могут быть и Extension Properties:

Большинство «магических» применений функций и лямбд в Kotlin, так же как и эта, являются не более чем синтаксическим сахаром, но каким удобным!

Лямбда-функции и анонимные функции

В Kotlin анонимные и лямбда-функции — это функции без имени, но при этом они могут быть использованы как объекты. Их можно писать прямо внутри выражения, минуя отдельное объявление.

Sportmaster Lab, Москва, от 170 000 ₽

Синтаксис декларации анонимной функции полностью совпадает с синтаксисом обычной функции, но у первой нет имени.

О семантических отличиях между этими видами функций мы поговорим позднее, а сейчас рассмотрим примеры использования лямбд вместе с функциями высшего порядка.

Функции высшего порядка

Функцией высшего порядка называют функцию, принимающую в качестве одного из аргументов другую функцию, в том числе лямбду или анонимную функцию. Яркий пример использования таких функций – callback.

Допустим, у нас есть функция высшего порядка longWork() :

Она принимает в качестве аргумента функцию callback() , но вызывает ее только после функции doSomething() . Использование этой функции:

Здесь мы вызываем функцию longWork() и передаем ей в качестве аргумента лямбда-функцию, которую она вызовет позже. Koltin позволяет вынести лямбду за скобки, если она — последний аргументом функции, а также вовсе убрать скобки, если лямбда является единственным аргументом. Также в большинстве случаев можно убрать тип возвращаемого значения и заменить аргументы на _ , если они не используются. Вот более короткий вариант:

Но можно и вовсе убрать аргументы у лямбда-функции, если они не нужны:

И это внешне напоминает уже не функцию высшего порядка, а языковую конструкцию, как например synchronized в Java. К слову, synchronized в Kotlin построен именно как функция высшего порядка.

Это очень удобно для создания так называемых DSL (Domain-Specific Languages) – предметно-ориентированных языков. Одни из самых популярных DSL для Kotlin — Anko (Android UI прямо в Kotlin с сохранением удобства XML-разметки), Gradle Kotlin DSL (Gradle-скрипты на Kotlin), kotlinx.html (по аналогии с Anko).

Для примера рассмотрим HTML-страницу на Kotlin:

В stdout будет выведено:

Главное преимущество этого DSL в том, что в отличие от декларативного HTML, в Kotlin есть переменные, которые могут быть использованы для генерации динамической страницы. И это намного красивее и удобнее классической генерации страницы через конкатенацию множества строк. В реальности для генерации HTML разметки используют другие методы, этот был показан только как пример DSL в Kotlin.

Другой пример использования функций высшего порядка – как аналог Streams API из Java:

Более сложные лямбды

Рассмотрим пример кода:

С помощью более сложных функций высшего порядка из стандартной библиотеки языка можно превратить код выше в следующее:

Как можно заметить, метод apply() позволяет не писать несколько раз builder.append() благодаря следующему прототипу метода:

public inline fun T.apply(block: T.() -> Unit): T

Здесь лямбда-функция block — метод-расширение для типа T , в данном случае для StringBuilder . И append() внутри лямбды block — это this.append() , где this – экземпляр класса StringBuilder .

Метод let() действует схожим образом, только принимает немного другую лямбду:

public inline fun T.let(block: (T) -> R): R

Здесь ссылка на объект передается не в качестве this , а в качестве явного аргумента метода, но мы его не указали. В таких случаях компилятор первый аргумент лямбда-функции автоматически называет «it».

Немного о недосказанном

Во-первых, в Kotlin, в отличие от Java, есть перегружаемые операторы. Так, например, если у класса есть метод plus() , то его можно вызвать оператором + , а метод get() – оператором [] .

Во-вторых, функции в Kotlin могут быть явно помечены как inline или noinline . Этот модификатор сообщает компилятору, стоит ли заинлайнить функцию для увеличения производительности или нет. Но здесь кроется подвох: разное поведение return в inline и noinline функциях.

Читайте также:  Playstation plus когда обновляются игры

В inline функциях return будет произведен из ближайшей по области видимости noinline функции. В noinline – из самой функции. Эту проблему решают «именованные return ».

Чтобы сделать return из лямбды, которую мы передаем в примере выше в apply() , можно использовать return@apply .

Именованными могут быть не только return , но и break , continue . Также можно создавать и собственные метки:

Кроме того, существует модификатор функции tailrec , который говорит компилятору заменить рекурсию в функции на цикл, если она написана в функциональном формате return if-then-else. Пример:

В-третьих, в случае, если метод требует в качестве аргументов объект, который должен быть унаследован от класса/интерфейса с одним абстрактным методом, то в эту функцию можно передать лямбду или анонимную функцию, а компилятор сам создаст анонимный класс с переопределением абстрактного метода на нашу лямбду. Например, в стандартной библиотеке Android есть метод public void setOnClickListener(View.OnClickListener l) , где OnClickListener – это интерфейс с единственным методом onClick(View v) .

Лямбда, переданная в виде setOnClickListener < doSomething() >, будет скомпилирована в анонимный класс, реализующий интерфейс OnClickListener , где наша лямбда превратится в метод onClick(View v) .

Итоги

Это далеко не всё о функциях в Kotlin, только самое часто используемое. Kotlin своими «магическими» функциями позволяет сильно упростить написание и, самое главное, чтение кода. Удобство написания и безопасность – это два самых важных отличия Kotlin от созданной ещё в 1995(!) году Java. В то время об удобстве и безопасности кода только мечтали.

Функции высшего порядка

Функция высшего порядка — это функция, которая принимает функции как параметры, или возвращает функцию в качестве результата. Хорошим примером такой функции является lock() , которая берёт блокирующий объект и функцию, получает блокировку, выполняет функцию и отпускает блокировку:

Проанализируем этот код: body имеет функциональный тип: () -> T , то есть параметр должен быть функцией без параметров, возвращающей значение типа T . Она вызывается внутри блока try , под защитой объекта lock , получившего блокировку вызовом функции lock() .

Если мы хотим вызвать метод lock() , можно передать другую функцию в качестве входящего аргумента (подробно читайте Ссылки на функции):

Обычно удобней передавать лямбда-выражения:

Лямбда-выражения подробно описаны здесь, но ради продолжения этого раздела сделаем краткий обзор:

  • Лямбда-выражение всегда заключено в фигурные скобки;
  • Его параметры (если они есть) объявлены до знака -> (допустимо не указывать параметры);
  • Тело выражения следует после знака -> .

В Kotlin есть конвенция, согласно которой, если последний параметр функции является функцией, которая передается в виде лямбда-выражения, можно вынести его за скобки:

Следующим примером функции высшего порядка выберем функцию map() :

Эту функцию можно вызвать так:

Обратите внимание, что скобки можно вообще не указывать при вызове функции, если лямбда является единственным аргументом.

Ключевое слово it : неявное имя единственного параметра

Ещё одна полезная конвенция состоит в том, что если функциональный литерал имеет ровно один параметр, его объявление можно удалить (вместе с -> ), и обращаться к нему по имени it :

Эти конвенции позволяют писать код в стиле LINQ:

Символ подчеркивания для неиспользуемых переменных (since 1.1)

Если параметр лямбды не используется, разрешено применять подчеркивание вместо его имени

Деструктуризация в лямбдах (since 1.1)

Деструктуризация в лямбдах описана в деструктурирующие объявления.

Инлайн функции

Иногда выгодно улучшить производительность функций высшего порядка, используя инлайн функции.

Лямбда-выражения и анонимные функции

Лямбда-выражение, или анонимная функция, это "функциональный литерал", то есть необъявленная функция, которая немедленно используется в качестве выражения. Рассмотрим следующий пример:

Функция max является функцией высшего порядка, потому что она принимает функцию в качестве второго аргумента. Этот второй аргумент является выражением, которое в свою очередь есть функция, то есть функциональный литерал. Как функция он эквивалентен объявлению:

Функциональные типы

Чтобы функция могла принять функцию в качестве параметра, необходимо указать тип функции-параметра. Например, вышеуказанная функция max определена так:

Параметр less является (T, T) -> Boolean типом, то есть функцией, которая принимает два параметра типа T и возвращает Boolean: ‘true’, если первый параметр меньше второго.

В теле функции, строка 4, less используется как функция: она вызвана с двумя параметрами типа T .

Функциональный тип записывается, как указано выше, или может иметь именованые параметры, если нужно выявить смысл каждого из параметров.

Синтаксис лямбда-выражений

Полная синтаксическая форма лямбда-выражений, таких как literals of function types, может быть представлена следующим образом:

Читайте также:  Btb16 600bw схема включения

sign. If the inferred return type of the lambda is not Unit, the last (or possibly single) expression inside the lambda body is treated as the return value.—>

Лямбда-выражение всегда заключено в скобки <. >, объявление параметров при таком синтаксисе происходит внутри этих скобок и может включать в себя аннотации типов (опционально), тело функции начинается после знака -> . Если тип возвращаемого значения не Unit , то в качестве возвращаемого типа принимается последнее (а возможно и единственное) выражение внутри тела лямбды.

Если мы вынесем все необязательные объявления, то, что останется, будет выглядеть следующим образом:

Обычное дело, когда лямбда-выражение имеет только один параметр. Если Kotlin может определить сигнатуру метода сам, он позволит нам не объявлять этот единственный параметр, и объявит его сам под именем it :

Мы можем явно вернуть значение из лямбды, используя qualified return синтаксис:

Обратите внимение, что функция принимает другую функцию в качестве своего последнего параметра, аргумент лямбда-выражения в таком случае может быть принят вне списка аргументов, заключённого в скобках. См. callSuffix.

Анонимные функции

Единственной особенностью синтаксиса лямбда-выражений, о которой ещё не было сказано, является способность определять и назначать возвращаемый функцией тип. В большинстве случаев в этом нет особой необходимости, потому что он может быть вычислен автоматически. Однако, если у вас есть потребность в определении возвращаемого типа, вы можете воспользоваться альтернативным синтаксисом:

Объявление анонимной функции выглядит очень похоже на обычное объявление функции, за исключением того, что её имя опущено. Тело такой функции может быть описано и выражением (как показано выше), и блоком:

Параметры функции и возвращаемый тип обозначаются таким же образом, как в обычных функциях. Правда, тип параметра может быть опущен, если его значение следует из контекста:

Аналогично и с типом возвращаемого значения: он вычисляется автоматически для функций-выражений или же должен быть определён вручную (если не является типом Unit ) для анонимных функций, которые имеют в себе блок.

Обратите внимание, что параметры анонимных функций всегда заключены в круглые скобки (. ) . Приём, позволяющий оставлять параметры вне скобок, работает только с лямбда-выражениями.

Одним из отличий лямбда-выражений от анонимных функций является поведение оператора return (non-local returns). Слово return , не имеющее метки ( @ ), всегда возвращается из функции, объявленной ключевым словом fun . Это означает, что return внутри лямбда-выражения возвратит выполнение к функции, включающей в себя это лямбда-выражение. Внутри анонимных функций оператор return , в свою очередь, выйдет, собственно, из анонимной функции.

Замыкания

Лямбда-выражение или анонимная функция (так же, как и локальная функция или object expression) имеет доступ к своему замыканию, то есть к переменным, объявленным вне этого выражения или функции. В отличие от Java, переменные, захваченные в замыкании, могут быть изменены:

Литералы функций с объектом-приёмником

Kotlin предоставляет возможность вызывать литерал функции с указаным объектом-приёмником. Внутри тела литерала вы можете вызывать методы объекта-приёмника без дополнительных определителей. Это схоже с принципом работы расширений, которые позволяют получить доступ к членам объекта-приёмника внутри тела функции. Один из самых важных примеров использования литералов с объектом-приёмником это Type-safe Groovy-style builders.

Тип такого литерала — это тип функции с приёмником:

По аналогии с расширениями, литерал функции может быть вызван так, будто он является методом объекта-приёмника:

Синтаксис анонимной функции позволяет вам явно указать тип приёмника. Это может быть полезно в случае, если вам нужно объявить переменную типа нашей функции для использования в дальнейшем.

Лямбда-выражения могут быть использованы как литералы функций с приёмником, когда тип приёмника может быть выведен из контекста.

Язык Kotlin имеет превосходную поддержку функционального программирования. Функции Kotlin могут храниться в переменных и структурах данных, передаваться в качестве аргументов и возвращаться из других функций более высокого порядка.

Функция высшего порядка —

В Kotlin функция, которая может принимать функцию в качестве параметра или может возвращать функцию, называется функцией высшего порядка . Вместо Integer, String или Array в качестве параметра функции мы передадим анонимную функцию или лямбду. Часто лямбды передаются в качестве параметра в функции Kotlin для удобства.

Передача лямбда-выражения в качестве параметра в функцию высшего порядка —

Мы можем передать лямбда-выражение в качестве параметра в функцию высшего порядка.
Существует два типа лямбда-выражения, которые можно передать:

  • Лямбда-выражение, которое возвращает единицу
  • Лямбда-выражение, которое возвращает любое из целочисленного значения, строки и т. Д.

Kotlin программа лямбда-выражения, которая возвращает Unit-

// функция высшего порядка

Читайте также:  1С дерево значений добавить строку

lmbd() // вызывает лямбда-выражение

>
fun main(args: Array ) <

// вызвать функцию высшего порядка

higherfunc(lambda) // передача лямбды в качестве параметра

Выход:

Объяснение:
Давайте разберемся с вышеприведенной программой шаг за шагом:

В верхней части мы определяем лямбда-выражение, которое содержит print() для вывода строки в стандартный вывод.

затем мы определяем функцию высшего порядка, которая содержит один параметр.

lmbd — это локальное имя для принимающего лямбда-параметра.
() означает, что функция не принимает никаких аргументов.
Unit представляет, что функция не возвращает никакого значения.

В основной функции мы вызвали функцию высшего порядка, передав лямбда-выражение в качестве параметра.

Kotlin программа лямбда-выражения, которая возвращает целочисленное значение —

// функция высшего порядка

var result = lmbd( 2 , 4 ) // вызывает лямбда-выражение, передавая параметры

println( "The sum of two numbers is: $result" )

fun main(args: Array ) <

higherfunc(lambda) // передача лямбды в качестве параметра

Выход:

Объяснение:
Давайте разберемся с вышеприведенной программой шаг за шагом:

В верхней части мы определяем лямбда-выражение, которое возвращает целочисленное значение.

Затем мы определили функцию высшего порядка, которая принимает лямбда-выражение в качестве параметра.

lmbd — это локальное имя для принимающего лямбда-параметра.
(Int, Int) представляет, что функция принимает два параметра целочисленного типа.
-> Int представляет, что функция возвращает целочисленное значение.

В основной функции мы вызвали функцию высшего порядка, передав лямбду в качестве параметра.

Передача функции в качестве параметра функции высшего порядка —

Мы можем передать функцию в качестве параметра в функцию высшего порядка.
Существует два типа функций, которые можно передавать:

  • функция, которая возвращает единицу
  • функция, которая возвращает любое целое значение, строку и т. д.

Kotlin программа прохождения функции, которая возвращает Unit-

// определение обычной функции

fun printMe(s:String): Unit<

// определение функции высшего порядка

fun higherfunc( str : String, myfunc: (String) -> Unit)<

// вызывать обычную функцию, используя локальное имя

>
fun main(args: Array ) <

// вызвать функцию высшего порядка

higherfunc( "GeeksforGeeks: A Computer Science portal for Geeks" . printMe)

Выход:

Объяснение:
Вверху мы определяем обычную функцию printMe() которая принимает параметр типа String и возвращает Unit.

(s: String) является единственным параметром
Единица представляет тип возврата

Затем мы определяем функцию высшего порядка как

он получает два параметра, один из которых имеет тип String, а другой — функцию
str: String представляет строковый параметр
myfunc: (String) -> Unit означает, что он принимает функцию в качестве параметра, который возвращает Unit.

Из основной функции старшая функция вызывается передачей строки и функции в качестве аргументов.

Kotlin программа передачи функции, которая возвращает целочисленное значение

// определение обычной функции

fun add(a: Int, b: Int): Int<

// определение функции высшего порядка

fun higherfunc(addfunc:(Int,Int)-> Int)<

// вызывать обычную функцию, используя локальное имя

var result = addfunc( 3 , 6 )

print( "The sum of two numbers is: $result" )

>
fun main(args: Array ) <

// вызвать функцию высшего порядка

Выход:

Объяснение:
В верхней части мы определяем регулярную функцию как

он принимает два параметра типа Integer и возвращает сумму обоих целых чисел

Затем мы определяем функцию высшего порядка как

он принимает функцию, которая содержит два параметра и
вызовите обычную функцию addfunc(3,6) , передав параметры.

Из основной функции мы вызываем функцию высшего порядка, передавая регулярную функцию в качестве параметра

Возврат функции из функции высшего порядка

Мы можем вернуть функцию из функции высшего порядка. При возврате функции мы должны указать типы параметров и тип возврата обычной функции в типе возврата функции высшего порядка.

Kotlin программа функции, возвращающей другую функцию

fun mul(a: Int, b: Int): Int<

// объявление функции высшего порядка

fun higherfunc() : ((Int,Int)-> Int)<

>
fun main(args: Array ) <

// вызвать функцию и сохранить возвращенную функцию в переменную

val multiply = higherfunc()

// вызывает функцию mul () путем передачи аргументов

val result = multiply( 2 , 4 )

println( "The multiplication of two numbers is: $result" )

Выход:

Объяснение:
В верхней части программы мы определяем функцию mul() которая принимает два параметра, а ее тип возвращаемого значения также является целым числом.

Затем мы определяем функцию высшего порядка с возвращаемым типом как функцию.

:: mul представляет, что возвращает функцию mul()
(Int, Int) представляет, что mul принимает два параметра целочисленного типа
Int представляет, что mul возвращает целочисленное значение.

В основной функции мы вызвали вышестоящую функцию, которая возвращает другую функцию и сохраняет ее в переменной multiply.

затем мы mulitply(2,4) функцию mul (), используя локальную переменную mulitply(2,4) , передавая два аргумента.

Ссылка на основную публикацию
Adblock detector