Что означает сигнатура функции Kotlin с T.()?
Это стандартная функция Котлина (насколько я знаю)
inline fun<T> with(t: T, body: T.() -> Unit) { t.body() }
Но может ли кто-нибудь писать на простом английском языке, что означает подпись? Это общая функция для T с первым аргументом "t" типа T, а во-вторых, "тело" типа функции, принимающим функцию???? и ничего не возвращает (Единица)
Я вижу это обозначение Something.() → Что-то используется довольно часто, то есть для Anko:
inline fun Activity.coordinatorLayout(init: CoordinatorLayout.() -> Unit) = ankoView({ CoordinatorLayout(it) },init)
но я не думаю, что это объяснялось где угодно.() означает...
Ответы
Ответ 1
T.() -> Unit
- это тип функции расширения с приемником.
Помимо обычных функций, Kotlin поддерживает функции расширения. Такая функция отличается от обычной тем, что имеет спецификацию типа приемника. Вот это общая часть T.
.
Ключевое слово this
внутри функции расширения соответствует объекту-получателю (объекту, который передается перед точкой), поэтому вы можете напрямую вызывать его методы (ссылка на this
из родительских областей все еще возможна с квалификаторами).
Функция with
стандартная, да. Это текущий код:
/**
* Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
*/
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
Таким образом, это универсальная функция для T
и R
с первым аргументом "receive" типа T
и вторым, f
типа функции расширения, который расширяет T
, возвращая тип R
который в свою очередь возвращается with
.
Например, вы можете использовать его так:
val threadInfoString = with (Thread.currentThread()) {
// isDaemon() & getPriority() are called with new property syntax for getters & setters
"${getName()}[isDaemon=$isDaemon,priority=$priority]"
}
Смотрите документацию по функциям расширения здесь:
kotlinlang.org/docs/reference/lambdas.html#extension-function-expressions
kotlinlang.org/docs/reference/scope-functions.html#with
kotlinlang.org/docs/reference/extensions.html
Добавлено:
Таким образом, единственно допустимым f
будет любая функция с 0 аргументами, определенная для T?
На самом деле, нет. В Kotlin типы функций и типы функций расширения унифицированы, так что они могут использоваться взаимозаменяемо. Например, мы можем передать String :: length, где ожидается функция (String) -> Int
.
// map() expects '(String) -> Int'
// argument has type 'String.() -> Int'
strings.map(String::length)
Типы, такие как Thread.() -> String
& (Thread) -> String
одинаковы со стороны фона - получатель, по сути, является первым аргументом.
Таким образом, любая из следующих функций подходит для аргумента Thread.() -> String
:
fun main(args: Array<String>) {
val f1 = fun Thread.(): String = name
val f2 = fun Thread.() = name
val f3: Thread.() -> String = { name }
val f4: (Thread) -> String = { it.name }
val f5 = { t: Thread -> t.name }
val f6: (Thread) -> String = Thread::getNameZ
val f7: Thread.() -> String = Thread::getNameZ
val f8 = Thread::getNameZ
}
fun Thread.getNameZ() = name
Или вы можете просто использовать функциональный литерал ({}
), как в примере с threadInfoString
, но он работает только тогда, когда тип получателя может быть выведен из контекста.
Ответ 2
Это еще одна удивительная концепция, называемая литералами функций с приемником. Она выглядит аналогично функции расширения с той разницей, что она имеет "приемник".
inline fun doSomethingWithInt(receiver: Int.() -> Unit) {
5.receiver() //Possible as receiver is Int type
receiver(5) //possible as receiver should be passed as first Argument
}
Пусть разбить это на части
(receiver: Int.() -> Unit)
Чем это отличается от обычной лямбды, как у () -> Unit
, есть ли у нее приемник типа Int
Так похоже на функцию Extension, этот получатель может вызывать по Int типу
5.receiver()
А согласно документации здесь
Значение типа функции может быть вызвано с помощью его invoke (...) оператор:
f.invoke(x) or just f(x).
Если значение имеет тип получателя, объект получателя должен быть передан в качестве первого аргумента. Другой способ вызвать значение функции тип с получателем должен предварять его получателем объекта, как если бы значение было функцией расширения: 1.foo(2)
Таким образом, вы также можете написать receiver(5)
Теперь мы можем написать код стиля DSL. Давайте посмотрим на стандартную библиотечную функцию kotlin Apply
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Как вы можете видеть, apply - это функция расширения T, блок будет работать с типом получателя T,
Из-за T.() T будет доступен в качестве первого аргумента в лямбда-выражении.
Теперь здесь блок вызывается block() внутри тела функции, но вы также можете написать this.block() или block (this)
И после вызова этой лямбды мы вернем экземпляр T (тот же экземпляр, для которого вызывается apply)
Таким образом, мы применили lambda в аргументе, выполнили его в том же экземпляре T, в котором он был вызван, и возвратили тот же экземпляр T
Пример
Вызов этой функции будет выглядеть следующим образом:
(StringBuilder("Hello ").apply({ append("Kotliner") }))
Но в kotlin, если лямбда является последним аргументом, тогда вместо того, чтобы писать как funName({})
, вы можете просто написать funName{}
.
поэтому приведенный выше код также может быть записан как
StringBuilder("Hello ").apply {
append("Kotliner")
append("! ")
}
который выглядит более четким и лаконичным. Таким образом, Kotlin обеспечивает структуру DSL, такую как Groovy.
Это очень хороший блог о той же теме