Как это связано с функциями расширения? Почему with
функцией , а не с ключевым словом?
Как представляется, нет явной документации для этой темы, а только предположение о знаниях в отношении расширений .
Это правда, что, по-видимому, существует мало существующей документации для концепции приемников (только небольшая заметка, связанная с функциями расширения ), что удивительно:
with
, которая не знает знаний получателей, может выглядеть как ключевое слово ; Все эти темы имеют документацию, но на приемниках ничего не происходит.
Первый:
Любой блок кода в Kotlin может иметь (или даже несколько) типы в качестве приемника , делая функции и свойства получателя доступными в этом блоке кода без его квалификации.
Представьте себе такой блок кода:
{ toLong() }
Не имеет большого смысла, не так ли? Фактически, присвоение этого типа функции (Int) -> Long
– где Int
является (единственным) параметром, а тип возврата – Long
– по праву может привести к ошибке компиляции. Вы можете исправить это, просто присвоив вызов функции с помощью неявного одиночного параметра. Однако для создания DSL это вызовет ряд проблем:
html { it.body { // how to access extensions of html here? } ... }
Это то, где приемники вступают в игру.
Назначив этот блок кода типу функции, который имеет Int
как приемник (а не как параметр!), Код неожиданно компилирует:
val intToLong: Int.() -> Long = { toLong() }
Что тут происходит?
Эта тема предполагает, что они знакомы с типами функций , но нужна небольшая заметка для приемников.
Типы функций также могут иметь один приемник, предварительно используя его тип и точку. Примеры:
Int.() -> Long // taking an integer as receiver producing a long String.(Long) -> String // taking a string as receiver and long as parameter producing a string GUI.() -> Unit // taking an GUI and producing nothing
Такие типы функций имеют список параметров, предваряемый типом приемника.
На самом деле невероятно просто понять, как обрабатываются блоки кода с приемниками:
Представьте, что, подобно функциям расширения, блок кода оценивается внутри класса типа приемника. это эффективно изменяется по типу приемника.
В нашем предыдущем примере val intToLong: Int.() -> Long = { toLong() }
, это фактически приводит к тому, что блок кода оценивается в другом контексте, как если бы он был помещен в функцию внутри Int
. Вот другой пример с использованием типов ручной работы, которые демонстрируют это лучше:
class Bar class Foo { fun transformToBar(): Bar = TODO() } val myBlockOfCodeWithReceiverFoo: (Foo).() -> Bar = { transformToBar() }
фактически становится (в уме, а не кодовым – вы не можете фактически расширять классы на JVM):
class Bar class Foo { fun transformToBar(): Bar = TODO() fun myBlockOfCode(): Bar { return transformToBar() } } val myBlockOfCodeWithReceiverFoo: (Foo) -> Bar = { it.myBlockOfCode() }
Обратите внимание, как внутри класса, мы не должны использовать this
для доступа к transformToBar
– то же самое происходит в блоке с приемником.
Так получилось, что в документации по этому вопросу также объясняется, как использовать самый внешний приемник, если текущий блок кода имеет два приемника, с помощью этого .
Да. Блок кода может иметь несколько приемников, но в настоящее время он не имеет выражения в системе типов. Единственный способ архивировать это – это несколько функций более высокого порядка, которые используют один тип функции приемника. Пример:
class Foo class Bar fun Foo.functionInFoo(): Unit = TODO() fun Bar.functionInBar(): Unit = TODO() inline fun higherOrderFunctionTakingFoo(body: (Foo).() -> Unit) = body(Foo()) inline fun higherOrderFunctionTakingBar(body: (Bar).() -> Unit) = body(Bar()) fun example() { higherOrderFunctionTakingFoo { higherOrderFunctionTakingBar { functionInFoo() functionInBar() } } }
Обратите внимание, что если эта функция языка Kotlin кажется неприемлемой для вашего DSL, @DslMarker – ваш друг!
Почему все это? Потому как:
Благодаря этим знаниям:
toLong()
в функции расширения на число, вместо того, чтобы каким-то образом ссылаться на номер. Может быть, ваша функция расширения не должна быть расширением? Объяснение Ф. Джорджа хорошее.
То, как я участвую, – это прочитать несколько разных учебников и статей, потому что каждый объясняет что-то по-другому, чем другой. Для тех из вас, кто ищет другой ресурс …
var greet: String.() -> Unit = { println("Hello $this") }
это определяет переменную типа String.() -> Unit
, которая сообщает вам
String
– это приемник () -> Unit
– это тип функции Как упоминалось выше, Ф. Джордж , все методы этого приемника можно вызвать в теле метода .
Итак, в нашем примере this
используется для печати String
. Функция может быть вызвана путем записи …
greet("Fitzgerald") // result is "Hello Fitzgerald"
приведенный фрагмент кода был взят из KOTLIN FUNCTION LITERALS WITH RECEIVER – БЫСТРОЕ ВВЕДЕНИЕ , Саймон Виртц.
Котлин поддерживает концепцию «функциональных литералов с приемниками». Он позволяет получать доступ к видимым методам и свойствам приемника лямбда в своем теле без каких-либо конкретных квалификаторов . Это очень похоже на функции расширения, в которых также возможен доступ к видимым членам объекта-получателя внутри расширения.
Простой пример, также являющийся одной из самых больших функций в стандартной библиотеке Котлин, apply
:
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Как вы можете видеть, такой литерал функции с приемником принимается как block
аргументов здесь. Этот блок просто выполняется и возвращается получатель (который является экземпляром T
). В действии это выглядит следующим образом:
val foo: Bar = Bar().apply { color = RED text = "Foo" }
Мы создаем экземпляр объекта Bar
и вызываем его. Экземпляр Bar
становится «приемником». block
, переданный в качестве аргумента в {}
(выражение лямбда), не должен использовать дополнительные квалификаторы для доступа и изменения отображаемых видимых свойств color
и text
.
Концепция lambdas с приемником также является самой важной функцией для написания DSL с Kotlin.