Что означает сигнатура функции 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.

Это очень хороший блог о той же теме